From 89a93c9516da56e03b74316ecfcf95ae4c23f488 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Thu, 18 May 2023 16:37:40 -0700 Subject: [PATCH] State store: make BulkSet/BulkDelete not transactional (#6342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated interfaces Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Add bulk routines for state stores Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Support passing BulkStoreOpts Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Updated gRPC APIs Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * 💄 Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Updated HTTP APIs Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Added replacement to go.mod Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Updated BulkSet in runtime too Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Updated pinned contrib Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Updated test app Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * 💄 Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Working on etag test Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * More tests Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Completed E2E test for HTTP API Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Bulk*WithOptions->Bulk* Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Added E2E tests for gRPC Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Simplified code Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Enabled E2E test Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * BulkStoreError contains key and not sequence Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Updated pinned contrib Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Pin contrib v1.11.0-rc.4 Signed-off-by: Bernd Verst * 💄 Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Fixed SDK test workflow Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Removed unnecessary go.od Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Fixed SDK workflow Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Fix lint. Signed-off-by: Artur Souza --------- Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Signed-off-by: Bernd Verst Signed-off-by: Artur Souza Co-authored-by: Yaron Schneider Co-authored-by: Bernd Verst Co-authored-by: Artur Souza --- .github/workflows/dapr-test-sdk.yml | 2 +- dapr/proto/components/v1/state.proto | 10 + go.mod | 81 ++- go.sum | 122 ++-- pkg/components/state/bulk.go | 146 +++++ pkg/components/state/bulk_test.go | 326 ++++++++++ pkg/components/state/pluggable.go | 10 +- pkg/components/state/pluggable_test.go | 12 +- pkg/grpc/api.go | 96 ++- pkg/grpc/api_test.go | 433 ++++++++++--- pkg/grpc/universalapi/api_state_query.go | 4 +- pkg/http/api.go | 37 +- pkg/http/api_test.go | 65 +- pkg/messages/predefined.go | 7 +- pkg/proto/components/v1/state.pb.go | 587 +++++++++++------- pkg/runtime/pubsub/bulkpublish_resiliency.go | 18 +- pkg/runtime/runtime.go | 14 +- pkg/testing/fake_state_store.go | 4 +- pkg/testing/state_mock.go | 10 +- tests/apps/perf/configuration/app.go | 6 +- tests/apps/perf/configuration/go.mod | 22 - tests/apps/perf/configuration/go.sum | 34 - tests/apps/resiliencyapp/go.mod | 4 +- tests/apps/resiliencyapp/go.sum | 8 +- tests/apps/resiliencyapp_grpc/go.mod | 4 +- tests/apps/resiliencyapp_grpc/go.sum | 8 +- tests/apps/secretapp/app.go | 2 +- .../go.mod | 4 +- .../go.sum | 8 +- tests/apps/stateapp/app.go | 568 +++++++++++++++-- tests/e2e/stateapp/stateapp_test.go | 47 +- tests/e2e/utils/helpers.go | 4 +- 32 files changed, 2019 insertions(+), 684 deletions(-) create mode 100644 pkg/components/state/bulk.go create mode 100644 pkg/components/state/bulk_test.go delete mode 100644 tests/apps/perf/configuration/go.mod delete mode 100644 tests/apps/perf/configuration/go.sum diff --git a/.github/workflows/dapr-test-sdk.yml b/.github/workflows/dapr-test-sdk.yml index 03f143e968a..e9a0b8cc597 100644 --- a/.github/workflows/dapr-test-sdk.yml +++ b/.github/workflows/dapr-test-sdk.yml @@ -103,7 +103,7 @@ jobs: uses: actions/setup-go@v4 with: go-version-file: "go.mod" - - name: Checkout p repo to run tests. + - name: Checkout python-sdk repo to run tests. uses: actions/checkout@v3 with: repository: dapr/python-sdk diff --git a/dapr/proto/components/v1/state.proto b/dapr/proto/components/v1/state.proto index 232d42dc281..ffedd990c29 100644 --- a/dapr/proto/components/v1/state.proto +++ b/dapr/proto/components/v1/state.proto @@ -236,8 +236,13 @@ message SetRequest { // reserved for future-proof extensibility message SetResponse {} +message BulkDeleteRequestOptions { + int64 parallelism = 1; +} + message BulkDeleteRequest { repeated DeleteRequest items = 1; + BulkDeleteRequestOptions options = 2; } // reserved for future-proof extensibility @@ -271,8 +276,13 @@ message BulkGetResponse { repeated BulkStateItem items = 1; } +message BulkSetRequestOptions { + int64 parallelism = 1; +} + message BulkSetRequest { repeated SetRequest items = 1; + BulkSetRequestOptions options = 2; } // reserved for future-proof extensibility diff --git a/go.mod b/go.mod index 43ed2e56503..4cf993c4cdb 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/PuerkitoBio/purell v1.2.0 github.com/argoproj/argo-rollouts v1.4.1 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/dapr/components-contrib v1.11.0-rc.1 - github.com/dapr/kit v0.0.5-0.20230418193628-15a7040dec41 + github.com/dapr/components-contrib v1.11.0-rc.4 + github.com/dapr/kit v0.0.5 github.com/evanphx/json-patch/v5 v5.6.0 github.com/fasthttp/router v1.4.18 github.com/go-logr/logr v1.2.4 @@ -39,12 +39,12 @@ require ( github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_model v0.3.0 github.com/prometheus/common v0.42.0 - github.com/redis/go-redis/v9 v9.0.3 + github.com/redis/go-redis/v9 v9.0.4 github.com/sony/gobreaker v0.5.0 - github.com/spf13/cast v1.5.0 + github.com/spf13/cast v1.5.1 github.com/stretchr/testify v1.8.2 github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde - github.com/valyala/fasthttp v1.45.0 + github.com/valyala/fasthttp v1.47.0 go.opencensus.io v0.24.0 go.opentelemetry.io/otel v1.14.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 @@ -55,9 +55,9 @@ require ( go.opentelemetry.io/otel/trace v1.14.0 go.uber.org/automaxprocs v1.5.2 go.uber.org/ratelimit v0.2.0 - golang.org/x/exp v0.0.0-20230419192730-864b3d6c5c2c - golang.org/x/net v0.9.0 - golang.org/x/sync v0.1.0 + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc + golang.org/x/net v0.10.0 + golang.org/x/sync v0.2.0 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.54.0 google.golang.org/protobuf v1.30.0 @@ -75,21 +75,6 @@ require ( sigs.k8s.io/yaml v1.3.0 ) -require ( - github.com/apache/thrift v0.13.0 // indirect - github.com/bytedance/gopkg v0.0.0-20220817015305-b879a72dc90f // indirect - github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762 // indirect - github.com/choleraehyq/pid v0.0.16 // indirect - github.com/cloudwego/fastpb v0.0.4-0.20230131074846-6fc453d58b96 // indirect - github.com/cloudwego/frugal v0.1.6 // indirect - github.com/cloudwego/kitex v0.5.0 // indirect - github.com/cloudwego/netpoll v0.3.2 // indirect - github.com/cloudwego/thriftgo v0.2.8 // indirect - github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect - github.com/oleiade/lane v1.0.1 // indirect - golang.org/x/arch v0.2.0 // indirect -) - require ( cloud.google.com/go v0.110.0 // indirect cloud.google.com/go/compute v1.19.0 // indirect @@ -104,22 +89,22 @@ require ( github.com/99designs/keyring v1.2.1 // indirect github.com/AthenZ/athenz v1.10.39 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0-beta.5 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v0.5.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5-0.20230428192423-86627ae445bc // indirect + github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5 // indirect github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v0.5.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.2.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid/v2 v2.0.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid/v2 v2.1.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.1.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v0.11.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v0.13.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v0.1.0 // indirect - github.com/Azure/go-amqp v0.18.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0 // indirect + github.com/Azure/go-amqp v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect @@ -151,25 +136,34 @@ require ( github.com/apache/dubbo-go-hessian2 v1.11.5 // indirect github.com/apache/pulsar-client-go v0.9.0 // indirect github.com/apache/rocketmq-client-go/v2 v2.1.2-0.20230412142645-25003f6f083d // indirect + github.com/apache/thrift v0.13.0 // indirect github.com/ardielle/ardielle-go v1.5.2 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/aws/aws-sdk-go v1.44.214 // indirect github.com/awslabs/kinesis-aggregation/go v0.0.0-20210630091500-54e17340d32f // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect + github.com/benbjohnson/clock v1.3.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.2.0 // indirect github.com/boltdb/bolt v1.3.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 // indirect github.com/bufbuild/protocompile v0.4.0 // indirect + github.com/bytedance/gopkg v0.0.0-20220817015305-b879a72dc90f // indirect github.com/camunda/zeebe/clients/go/v8 v8.1.8 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chebyrash/promise v0.0.0-20220530143319-1123826567d6 // indirect + github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762 // indirect + github.com/choleraehyq/pid v0.0.16 // indirect github.com/clbanning/mxj/v2 v2.5.6 // indirect github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.13.0 // indirect github.com/cloudevents/sdk-go/v2 v2.13.0 // indirect + github.com/cloudwego/fastpb v0.0.4-0.20230131074846-6fc453d58b96 // indirect + github.com/cloudwego/frugal v0.1.6 // indirect + github.com/cloudwego/kitex v0.5.0 // indirect + github.com/cloudwego/netpoll v0.3.2 // indirect + github.com/cloudwego/thriftgo v0.2.8 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect github.com/creasty/defaults v1.5.2 // indirect @@ -212,7 +206,7 @@ require ( github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -230,6 +224,7 @@ require ( github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.8.0 // indirect @@ -315,6 +310,7 @@ require ( github.com/nats-io/nkeys v0.3.0 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/stan.go v0.10.4 // indirect + github.com/oleiade/lane v1.0.1 // indirect github.com/open-policy-agent/opa v0.49.2 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/openzipkin/zipkin-go v0.4.1 // indirect @@ -338,7 +334,7 @@ require ( github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect github.com/shirou/gopsutil/v3 v3.22.2 // indirect - github.com/sijms/go-ora/v2 v2.6.11 // indirect + github.com/sijms/go-ora/v2 v2.7.6 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cobra v1.6.1 // indirect @@ -379,14 +375,15 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.8.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/term v0.7.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/api v0.115.0 // indirect @@ -410,11 +407,11 @@ require ( lukechampine.com/uint128 v1.3.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.22.3 // indirect + modernc.org/libc v1.22.6 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect modernc.org/opt v0.1.3 // indirect - modernc.org/sqlite v1.21.1 // indirect + modernc.org/sqlite v1.22.1 // indirect modernc.org/strutil v1.1.3 // indirect modernc.org/token v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/go.sum b/go.sum index 3636ef1fc4d..b9c80963467 100644 --- a/go.sum +++ b/go.sum @@ -74,28 +74,30 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0 h1:xGLAFFd9D3iLGxYiUGPdITSzsFmU1K8VtfuUHWAoN7M= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0-beta.5 h1:F8ii3ek6K2tnf9gmv/YFktyOci9DuJboh/rKXMS2FaQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0-beta.5/go.mod h1:ZJteiLBLt8CmYc6yJFe5YErRHQ4FpTEwgXomR1ikcy8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v0.5.0 h1:OrKZybbyagpgJiREiIVzH5mV/z9oS4rXqdX7i31DSF0= github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v0.5.0/go.mod h1:p74+tP95m8830ypJk53L93+BEsjTKY4SKQ75J2NmS5U= -github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5-0.20230428192423-86627ae445bc h1:DKHf95u7GvzAM8D+n+/R3saFpnA5ldUwMmen+eI9acQ= -github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5-0.20230428192423-86627ae445bc/go.mod h1:Beh5cHIXJ0oWEDWk9lNFtuklCojLLQ5hl+LqSNTTs0I= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5 h1:qS0Bp4do0cIvnuQgSGeO6ZCu/q/HlRKl4NPfv1eJ2p0= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5/go.mod h1:Beh5cHIXJ0oWEDWk9lNFtuklCojLLQ5hl+LqSNTTs0I= github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1 h1:bFa9IcjvrCber6gGgDAUZ+I2bO8J7s8JxXmu9fhi2ss= github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1/go.mod h1:l3wvZkG9oW07GLBW5Cd0WwG5asOfJ8aqE8raUvNzLpk= github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= -github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v0.5.0 h1:v4v4ccQInOrQ1dT6Z1jhmSfv/Vo+Gj6TiH4agar4+9c= -github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v0.5.0/go.mod h1:N1KaFfTg2o6ltJ2djIz5oOFE9tgHOHqQR+dIOiAdyUc= -github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.2.1 h1:ryVRjO3SrGrSM8PNlLuMbMYFz9vexPzvenNUEBfsgCo= -github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.2.1/go.mod h1:R6+0udeRV8iYSTVuT5RT7If4sc46K5Bz3ZKrmvZQF7U= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid/v2 v2.0.0 h1:PcP4TC+0dC85A3i1p7CbD0FyrjnTvzQ3ipgSkJTIb7Y= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid/v2 v2.0.0/go.mod h1:rUn3oBeYkTxIsUzKxtXUPOt1ZO+bVHuZ3m5wauIfUHw= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0.0 h1:BWeAAEzkCnL0ABVJqs+4mYudNch7oFGPtTlSmIWL8ms= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0.0/go.mod h1:Y3gnVwfaz8h6L1YHar+NfWORtBoVUSB5h4GlGkdeF7Q= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.0.0 h1:IQPFvZDfowjuv77a987bsErW+RjE1YbR3mpcYD5K2to= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.0.0/go.mod h1:fswVBSaYFoW4XXp3oXG0vuDVdToLr3kRzgp5oePMq5g= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.3.0 h1:eLqmA+3GXbOOLJVTSqkrFudTbHkfOp5HIy+iShCNM7A= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.3.0/go.mod h1:pXDkeh10bAqElvd+S5Ppncj+DCKvJGXNa8rRT2R7rIw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid/v2 v2.1.1 h1:q8d6Cw16DrwJ+o82GMEQ+xt65q7w4m7VcI4C+gK/7Jk= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid/v2 v2.1.1/go.mod h1:ZHJdpjiGjZBBILAyAUTP93YSLF/Foo1J72HSx30gMeQ= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.1.1 h1:gZ1ZZvrVUhDNsGNpbo2N87Y0CJB8p3IS5UH9Z4Ui97g= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.1.1/go.mod h1:7fQVOnRA11ScLE8dOCWanXHQa2NMFOM2i0u/1VRICXA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v0.11.0 h1:efdSCWUBtk2FUUIlEfZhRQyVIM3Ts8lA3vaF18amnwo= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v0.11.0/go.mod h1:LLJYu/UhJ8GpH5PtJc06RmJ1gJ5mPCSc1PiDMW17MHM= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v0.13.0 h1:XY0plaTx8oeipK+XogAck2Qzv39KdnJNBwrxC4A0GL4= @@ -104,10 +106,10 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028g github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag= -github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v0.1.0 h1:TOtQFiO403wClfrZId/EKvlKOfUhL0mWgvWgZ0FNn8I= -github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v0.1.0/go.mod h1:U0IH4deB/maBcagR9SiNeIfgZ1BY/zYCq8SOiQ4vfRc= -github.com/Azure/go-amqp v0.18.1 h1:D5Ca+uijuTcj5g76sF+zT4OQZcFFY397+IGf/5Ip5Sc= -github.com/Azure/go-amqp v0.18.1/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= +github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0 h1:lJwNFV+xYjHREUTHJKx/ZF6CJSt9znxmLw9DqSTvyRU= +github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0/go.mod h1:GfT0aGew8Qj5yiQVqOO5v7N8fanbJGyUoHqXg56qcVY= +github.com/Azure/go-amqp v1.0.0 h1:QfCugi1M+4F2JDTRgVnRw7PYXLXZ9hmqk3+9+oJh3OA= +github.com/Azure/go-amqp v1.0.0/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= @@ -270,8 +272,8 @@ github.com/awslabs/kinesis-aggregation/go v0.0.0-20210630091500-54e17340d32f/go. github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.4 h1:wj3BFPrTw8yYgA1OlMqvUk95nc8OMv3cvBSF5erT2W4= +github.com/benbjohnson/clock v1.3.4/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -399,10 +401,10 @@ github.com/dancannon/gorethink v4.0.0+incompatible h1:KFV7Gha3AuqT+gr0B/eKvGhbjm github.com/dancannon/gorethink v4.0.0+incompatible/go.mod h1:BLvkat9KmZc1efyYwhz3WnybhRZtgF1K929FD8z1avU= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= -github.com/dapr/components-contrib v1.11.0-rc.1 h1:hvezI22Vn2YWUpV2qjCc9GAIhUdxnZzTY/6exWdALNU= -github.com/dapr/components-contrib v1.11.0-rc.1/go.mod h1:cyzQOooKBS11WMGAtnIw1uNn6FbImC7ndwvyZa993Gc= -github.com/dapr/kit v0.0.5-0.20230418193628-15a7040dec41 h1:G55KmJAn2UsJm2+T0Vyz8gPcbphDe1PvjrES18JxfHQ= -github.com/dapr/kit v0.0.5-0.20230418193628-15a7040dec41/go.mod h1:VTWiX6nk5RWZ/kqEJD/EGjJU8hdwHZ73r75K510OHBY= +github.com/dapr/components-contrib v1.11.0-rc.4 h1:+MexV5pqeBxqCWcmAMwHCENl8DosAZhND9UDiLBK15A= +github.com/dapr/components-contrib v1.11.0-rc.4/go.mod h1:AwbfpZkJttIxPGGtg4tDyXz7izzjca0xfHbzixazLmw= +github.com/dapr/kit v0.0.5 h1:BbjO6LksdXAv6iuTVZiHJNkXj9Ii1gS2wA1u785Ypsg= +github.com/dapr/kit v0.0.5/go.mod h1:VTWiX6nk5RWZ/kqEJD/EGjJU8hdwHZ73r75K510OHBY= github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -509,7 +511,7 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -600,8 +602,8 @@ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPr github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -1015,8 +1017,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -1360,8 +1362,8 @@ github.com/rabbitmq/amqp091-go v1.7.0/go.mod h1:wfClAtY0C7bOHxd3GjmF26jEHn+rR/0B github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= -github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc= +github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -1400,8 +1402,8 @@ github.com/shirou/gopsutil/v3 v3.21.6/go.mod h1:JfVbDpIBLVzT8oKbvMg9P3wEIMDDpVn+ github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks= github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sijms/go-ora/v2 v2.6.11 h1:inBa/Tp0/kEl2prd3p5VabDXvmgVEelg328RYwsOCiE= -github.com/sijms/go-ora/v2 v2.6.11/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= +github.com/sijms/go-ora/v2 v2.7.6 h1:QyR1CKFxG+VVk2+LdHoHF4NxDSvcQ3deBXtZCrahSq4= +github.com/sijms/go-ora/v2 v2.7.6/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1431,8 +1433,8 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= @@ -1528,8 +1530,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= -github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= +github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -1674,8 +1676,9 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c= golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= -golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1704,8 +1707,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1721,8 +1724,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230419192730-864b3d6c5c2c h1:HDdYQYKOkvJT/Plb5HwJJywTVyUnIctjQm6XSnZ/0CY= -golang.org/x/exp v0.0.0-20230419192730-864b3d6c5c2c/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1836,8 +1839,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1853,8 +1856,8 @@ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1868,8 +1871,9 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1988,16 +1992,16 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2101,8 +2105,8 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2371,23 +2375,23 @@ modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= -modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +modernc.org/libc v1.22.6 h1:cbXU8R+A6aOjRuhsFh3nbDWXO/Hs4ClJRXYB11KmPDo= +modernc.org/libc v1.22.6/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU= -modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= +modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE= +modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= -nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/components/state/bulk.go b/pkg/components/state/bulk.go new file mode 100644 index 00000000000..3b4aab04f13 --- /dev/null +++ b/pkg/components/state/bulk.go @@ -0,0 +1,146 @@ +/* +Copyright 2023 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 state + +import ( + "context" + "errors" + "sync/atomic" + + "github.com/cenkalti/backoff/v4" + + "github.com/dapr/components-contrib/state" + "github.com/dapr/dapr/pkg/resiliency" +) + +type stateRequestConstraint interface { + state.SetRequest | state.DeleteRequest + state.StateRequest +} + +func requestWithKey[T stateRequestConstraint](reqs []T, key string) int { + for i, r := range reqs { + if r.GetKey() == key { + return i + } + } + + // Should never happen… + return -1 +} + +// PerformBulkStoreOperation performs a bulk set or delete using resiliency, retrying operations that fail only when they can be retried. +func PerformBulkStoreOperation[T stateRequestConstraint]( + ctx context.Context, reqs []T, policyDef *resiliency.PolicyDefinition, opts state.BulkStoreOpts, + execSingle func(ctx context.Context, req *T) error, + execMulti func(ctx context.Context, reqs []T, opts state.BulkStoreOpts) error, +) error { + var reqsAtomic atomic.Pointer[[]T] + reqsAtomic.Store(&reqs) + policyRunner := resiliency.NewRunnerWithOptions(ctx, + policyDef, + resiliency.RunnerOpts[[]string]{ + // In case of errors, the policy runner function returns a list of items that are to be retried. + // Items that can NOT be retried are the items that either succeeded (when at least one other item failed) or which failed with an etag error (which can't be retried) + Accumulator: func(retry []string) { + rReqs := *reqsAtomic.Load() + newReqs := make([]T, len(retry)) + var n int + for _, retryKey := range retry { + i := requestWithKey(reqs, retryKey) + if i >= 0 { + newReqs[n] = rReqs[i] + n++ + } + } + newReqs = newReqs[:n] + reqsAtomic.Store(&newReqs) + }, + }, + ) + _, err := policyRunner(func(ctx context.Context) ([]string, error) { + var rErr error + rReqs := *reqsAtomic.Load() + + // If there's a single request, perform it in non-bulk + // In this case, we never need to filter out operations + if len(rReqs) == 1 { + rErr = execSingle(ctx, &rReqs[0]) + if rErr != nil { + // Check if it's an etag error, which is not retriable + // In that case, wrap inside a permanent backoff error + var etagErr *state.ETagError + if errors.As(rErr, &etagErr) { + rErr = backoff.Permanent(rErr) + } + } + return nil, rErr + } + + // Perform the request in bulk + rErr = execMulti(ctx, rReqs, opts) + + // If there's no error, short-circuit + if rErr == nil { + return nil, nil + } + + // Check if we have a multi-error; if not, return the error as-is + mErr, ok := rErr.(interface{ Unwrap() []error }) + if !ok { + return nil, rErr + } + errs := mErr.Unwrap() + if len(errs) == 0 { + // Should never happen… + return nil, rErr + } + + // Check which operation(s) failed + // We can retry if at least one error is not an etag error + var canRetry, etagInvalid bool + retry := make([]string, 0, len(errs)) + for _, e := range errs { + // Check if it's a BulkStoreError + // If not, we will cause all operations to be retried, because the error was not a multi BulkStoreError + var bse state.BulkStoreError + if !errors.As(e, &bse) { + return nil, rErr + } + + // If it's not an etag error, the operation can retry this failed item + if etagErr := bse.ETagError(); etagErr == nil { + canRetry = true + retry = append(retry, bse.Key()) + } else if etagErr.Kind() == state.ETagInvalid { + // If at least one etag error is due to an etag invalid, record that + etagInvalid = true + } + } + + // If canRetry is false, it means that all errors are etag errors, which are permanent + if !canRetry { + var etagErr *state.ETagError + if etagInvalid { + etagErr = state.NewETagError(state.ETagInvalid, rErr) + } else { + etagErr = state.NewETagError(state.ETagMismatch, rErr) + } + rErr = backoff.Permanent(etagErr) + } + return retry, rErr + }) + + return err +} diff --git a/pkg/components/state/bulk_test.go b/pkg/components/state/bulk_test.go new file mode 100644 index 00000000000..6882887312e --- /dev/null +++ b/pkg/components/state/bulk_test.go @@ -0,0 +1,326 @@ +/* +Copyright 2023 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 state + +import ( + "context" + "errors" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/dapr/components-contrib/state" + resiliencyV1alpha1 "github.com/dapr/dapr/pkg/apis/resiliency/v1alpha1" + "github.com/dapr/dapr/pkg/resiliency" + "github.com/dapr/kit/logger" + "github.com/dapr/kit/ptr" +) + +func TestPerformBulkStoreOperation(t *testing.T) { + simulatedErr := errors.New("simulated") + etagMismatchErr := state.NewETagError(state.ETagMismatch, simulatedErr) + etagInvalidErr := state.NewETagError(state.ETagInvalid, simulatedErr) + + res := resiliency.FromConfigurations(logger.NewLogger("test"), &resiliencyV1alpha1.Resiliency{ + Spec: resiliencyV1alpha1.ResiliencySpec{ + Policies: resiliencyV1alpha1.Policies{ + Retries: map[string]resiliencyV1alpha1.Retry{ + "singleRetry": { + MaxRetries: ptr.Of(1), + MaxInterval: "100ms", + Policy: "constant", + Duration: "10ms", + }, + }, + Timeouts: map[string]string{ + "fast": "100ms", + }, + }, + Targets: resiliencyV1alpha1.Targets{ + Components: map[string]resiliencyV1alpha1.ComponentPolicyNames{ + "mystate": { + Outbound: resiliencyV1alpha1.PolicyNames{ + Retry: "singleRetry", + Timeout: "fast", + }, + }, + }, + }, + }, + }) + policyDef := res.ComponentOutboundPolicy("mystate", resiliency.Statestore) + + t.Run("single request", func(t *testing.T) { + reqs := []state.SetRequest{ + {Key: "key1"}, + } + + t.Run("no error", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + func(ctx context.Context, req *state.SetRequest) error { + count.Add(1) + return nil + }, + nil, // The multi method should not be invoked, so this will panic if it happens + ) + require.NoError(t, err) + require.Equal(t, uint32(1), count.Load()) + }) + + t.Run("does not retry on etag error", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + func(ctx context.Context, req *state.SetRequest) error { + count.Add(1) + return etagInvalidErr + }, + nil, // The multi method should not be invoked, so this will panic if it happens + ) + var etagErr *state.ETagError + require.Error(t, err) + require.ErrorAs(t, err, &etagErr) + require.Equal(t, uint32(1), count.Load()) + }) + + t.Run("retries on other errors", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + func(ctx context.Context, req *state.SetRequest) error { + count.Add(1) + return simulatedErr + }, + nil, // The multi method should not be invoked, so this will panic if it happens + ) + require.Error(t, err) + require.Equal(t, uint32(2), count.Load()) + }) + + t.Run("success on second attempt", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + func(ctx context.Context, req *state.SetRequest) error { + if count.Add(1) == 1 { + return simulatedErr + } + return nil + }, + nil, // The multi method should not be invoked, so this will panic if it happens + ) + require.NoError(t, err) + require.Equal(t, uint32(2), count.Load()) + }) + }) + + t.Run("multiple requests", func(t *testing.T) { + reqs := []state.SetRequest{ + {Key: "key1"}, + {Key: "key2"}, + } + + t.Run("all successful", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + nil, // The single method should not be invoked, so this will panic if it happens + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return nil + }, + ) + require.NoError(t, err) + require.Equal(t, uint32(1), count.Load()) + }) + + t.Run("key1 successful, key2 etag mismatch", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + nil, // The single method should not be invoked, so this will panic if it happens + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return errors.Join( + state.NewBulkStoreError("key2", etagMismatchErr), + ) + }, + ) + require.Error(t, err) + var etagErr *state.ETagError + require.ErrorAs(t, err, &etagErr) + require.Equal(t, state.ETagMismatch, etagErr.Kind()) + require.Equal(t, uint32(1), count.Load()) + }) + + t.Run("key1 etag invalid, key2 etag mismatch", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + nil, // The single method should not be invoked, so this will panic if it happens + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return errors.Join( + state.NewBulkStoreError("key1", etagInvalidErr), + state.NewBulkStoreError("key2", etagMismatchErr), + ) + }, + ) + require.Error(t, err) + var etagErr *state.ETagError + require.ErrorAs(t, err, &etagErr) + require.Equal(t, state.ETagInvalid, etagErr.Kind()) + require.Equal(t, uint32(1), count.Load()) + }) + + t.Run("key1 successful, key2 fails and is retried", func(t *testing.T) { + count := atomic.Uint32{} + // This should retry, but the second time only key2 should be requested + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + func(ctx context.Context, req *state.SetRequest) error { + require.Equal(t, "key2", req.Key) + count.Add(1) + return simulatedErr + }, + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return errors.Join( + state.NewBulkStoreError("key2", simulatedErr), + ) + }, + ) + require.Error(t, err) + require.Equal(t, simulatedErr, err) + require.Equal(t, uint32(2), count.Load()) + }) + + t.Run("key1 fails and is retried, key2 has etag error", func(t *testing.T) { + count := atomic.Uint32{} + // This should retry, but the second time only key1 should be requested + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + func(ctx context.Context, req *state.SetRequest) error { + require.Equal(t, "key1", req.Key) + count.Add(1) + return simulatedErr + }, + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return errors.Join( + state.NewBulkStoreError("key1", simulatedErr), + state.NewBulkStoreError("key2", etagMismatchErr), + ) + }, + ) + require.Error(t, err) + require.Equal(t, simulatedErr, err) + require.Equal(t, uint32(2), count.Load()) + }) + + t.Run("key1 fails and is retried, key2 has etag error, key3 succeeds", func(t *testing.T) { + reqs2 := []state.SetRequest{ + {Key: "key1"}, + {Key: "key2"}, + {Key: "key3"}, + } + + count := atomic.Uint32{} + // This should retry, but the second time only key1 should be requested + err := PerformBulkStoreOperation(context.Background(), reqs2, policyDef, state.BulkStoreOpts{}, + func(ctx context.Context, req *state.SetRequest) error { + require.Equal(t, "key1", req.Key) + count.Add(1) + return simulatedErr + }, + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return errors.Join( + state.NewBulkStoreError("key1", simulatedErr), + state.NewBulkStoreError("key2", etagMismatchErr), + ) + }, + ) + require.Error(t, err) + require.Equal(t, simulatedErr, err) + require.Equal(t, uint32(2), count.Load()) + }) + + t.Run("key1 succeeds on 2nd attempt, key2 succeeds, key3 has etag error on 2nd attempt", func(t *testing.T) { + reqs2 := []state.SetRequest{ + {Key: "key1"}, + {Key: "key2"}, + {Key: "key3"}, + } + + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs2, policyDef, state.BulkStoreOpts{}, + nil, // The single method should not be invoked, so this will panic if it happens + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + if count.Add(1) == 1 { + // On first attempt, key1 and key3 fail with non-etag errors + return errors.Join( + state.NewBulkStoreError("key1", simulatedErr), + state.NewBulkStoreError("key3", simulatedErr), + ) + } + + // On the second attempt, key3 fails with etag error + require.Len(t, req, 2) + for i := 0; i < 2; i++ { + switch req[i].Key { + case "key3", "key1": + // All good + default: + t.Fatalf("Found unexpected key: %s", req[i].Key) + } + } + return errors.Join( + state.NewBulkStoreError("key3", etagMismatchErr), + ) + }, + ) + require.Error(t, err) + var etagErr *state.ETagError + require.ErrorAs(t, err, &etagErr) + require.Equal(t, state.ETagMismatch, etagErr.Kind()) + require.Equal(t, uint32(2), count.Load()) + }) + + t.Run("retries when error is not a multierror", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + nil, // The single method should not be invoked, so this will panic if it happens + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return simulatedErr + }, + ) + require.Error(t, err) + require.Equal(t, simulatedErr, err) + require.Equal(t, uint32(2), count.Load()) + }) + + t.Run("retries when multierror contains a non-BulkStoreError error", func(t *testing.T) { + count := atomic.Uint32{} + err := PerformBulkStoreOperation(context.Background(), reqs, policyDef, state.BulkStoreOpts{}, + nil, // The single method should not be invoked, so this will panic if it happens + func(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { + count.Add(1) + return errors.Join(simulatedErr) + }, + ) + require.Error(t, err) + merr, ok := err.(interface{ Unwrap() []error }) + require.True(t, ok) + require.Len(t, merr.Unwrap(), 1) + require.Equal(t, merr.Unwrap()[0], simulatedErr) + require.Equal(t, uint32(2), count.Load()) + }) + }) +} diff --git a/pkg/components/state/pluggable.go b/pkg/components/state/pluggable.go index 836e7bbbf67..de7fe88f781 100644 --- a/pkg/components/state/pluggable.go +++ b/pkg/components/state/pluggable.go @@ -223,7 +223,7 @@ func (ss *grpcStateStore) Set(ctx context.Context, req *state.SetRequest) error } // BulkDelete performs a delete operation for many keys at once. -func (ss *grpcStateStore) BulkDelete(ctx context.Context, reqs []state.DeleteRequest) error { +func (ss *grpcStateStore) BulkDelete(ctx context.Context, reqs []state.DeleteRequest, opts state.BulkStoreOpts) error { protoRequests := make([]*proto.DeleteRequest, len(reqs)) for idx := range reqs { @@ -232,6 +232,9 @@ func (ss *grpcStateStore) BulkDelete(ctx context.Context, reqs []state.DeleteReq bulkDeleteRequest := &proto.BulkDeleteRequest{ Items: protoRequests, + Options: &proto.BulkDeleteRequestOptions{ + Parallelism: int64(opts.Parallelism), + }, } _, err := ss.Client.BulkDelete(ctx, bulkDeleteRequest) @@ -272,7 +275,7 @@ func (ss *grpcStateStore) BulkGet(ctx context.Context, req []state.GetRequest, o } // BulkSet performs a set operation for many keys at once. -func (ss *grpcStateStore) BulkSet(ctx context.Context, req []state.SetRequest) error { +func (ss *grpcStateStore) BulkSet(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { requests := []*proto.SetRequest{} for idx := range req { protoRequest, err := toSetRequest(&req[idx]) @@ -283,6 +286,9 @@ func (ss *grpcStateStore) BulkSet(ctx context.Context, req []state.SetRequest) e } _, err := ss.Client.BulkSet(ctx, &proto.BulkSetRequest{ Items: requests, + Options: &proto.BulkSetRequestOptions{ + Parallelism: int64(opts.Parallelism), + }, }) return mapBulkSetErrs(err) } diff --git a/pkg/components/state/pluggable_test.go b/pkg/components/state/pluggable_test.go index d9236a434ea..ef38a169453 100644 --- a/pkg/components/state/pluggable_test.go +++ b/pkg/components/state/pluggable_test.go @@ -483,7 +483,7 @@ func TestComponentCalls(t *testing.T) { require.NoError(t, err) defer cleanup() - err = stStore.BulkSet(context.Background(), []state.SetRequest{}) + err = stStore.BulkSet(context.Background(), []state.SetRequest{}, state.BulkStoreOpts{}) assert.NotNil(t, err) assert.Equal(t, int64(1), svc.bulkSetCalled.Load()) @@ -504,7 +504,7 @@ func TestComponentCalls(t *testing.T) { require.NoError(t, err) defer cleanup() - err = stStore.BulkSet(context.Background(), requests) + err = stStore.BulkSet(context.Background(), requests, state.BulkStoreOpts{}) assert.ErrorIs(t, ErrNilSetValue, err) assert.Equal(t, int64(0), svc.bulkSetCalled.Load()) @@ -531,7 +531,7 @@ func TestComponentCalls(t *testing.T) { require.NoError(t, err) defer cleanup() - err = stStore.BulkSet(context.Background(), requests) + err = stStore.BulkSet(context.Background(), requests, state.BulkStoreOpts{}) require.NoError(t, err) assert.Equal(t, int64(1), svc.bulkSetCalled.Load()) @@ -556,7 +556,7 @@ func TestComponentCalls(t *testing.T) { require.NoError(t, err) defer cleanup() - err = stStore.BulkDelete(context.Background(), requests) + err = stStore.BulkDelete(context.Background(), requests, state.BulkStoreOpts{}) require.NoError(t, err) assert.Equal(t, int64(1), svc.bulkDeleteCalled.Load()) @@ -578,7 +578,7 @@ func TestComponentCalls(t *testing.T) { require.NoError(t, err) defer cleanup() - err = stStore.BulkDelete(context.Background(), requests) + err = stStore.BulkDelete(context.Background(), requests, state.BulkStoreOpts{}) assert.NotNil(t, err) assert.Equal(t, int64(1), svc.bulkDeleteCalled.Load()) @@ -610,7 +610,7 @@ func TestComponentCalls(t *testing.T) { require.NoError(t, err) defer cleanup() - err = stStore.BulkDelete(context.Background(), requests) + err = stStore.BulkDelete(context.Background(), requests, state.BulkStoreOpts{}) assert.NotNil(t, err) _, ok := err.(*state.BulkDeleteRowMismatchError) diff --git a/pkg/grpc/api.go b/pkg/grpc/api.go index 34a2d13d8a3..0388cf8b4bc 100644 --- a/pkg/grpc/api.go +++ b/pkg/grpc/api.go @@ -503,7 +503,7 @@ func (a *api) InvokeBinding(ctx context.Context, in *runtimev1pb.InvokeBindingRe func (a *api) GetBulkState(ctx context.Context, in *runtimev1pb.GetBulkStateRequest) (*runtimev1pb.GetBulkStateResponse, error) { bulkResp := &runtimev1pb.GetBulkStateResponse{} - store, err := a.getStateStore(in.StoreName) + store, err := a.UniversalAPI.GetStateStore(in.StoreName) if err != nil { // Error has already been logged return bulkResp, err @@ -513,7 +513,6 @@ func (a *api) GetBulkState(ctx context.Context, in *runtimev1pb.GetBulkStateRequ return bulkResp, nil } - // try bulk get first var key string reqs := make([]state.GetRequest, len(in.Keys)) for i, k := range in.Keys { @@ -529,7 +528,7 @@ func (a *api) GetBulkState(ctx context.Context, in *runtimev1pb.GetBulkStateRequ } start := time.Now() - policyDef := a.resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore) + policyDef := a.UniversalAPI.Resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore) bgrPolicyRunner := resiliency.NewRunner[[]state.BulkGetResponse](ctx, policyDef) responses, err := bgrPolicyRunner(func(ctx context.Context) ([]state.BulkGetResponse, error) { return store.BulkGet(ctx, reqs, state.BulkGetOpts{ @@ -578,24 +577,8 @@ func (a *api) GetBulkState(ctx context.Context, in *runtimev1pb.GetBulkStateRequ return bulkResp, nil } -func (a *api) getStateStore(name string) (state.Store, error) { - if a.CompStore.StateStoresLen() == 0 { - err := messages.ErrStateStoresNotConfigured - apiServerLogger.Debug(err) - return nil, err - } - - state, ok := a.CompStore.GetStateStore(name) - if !ok { - err := messages.ErrStateStoreNotFound.WithFormat(name) - apiServerLogger.Debug(err) - return nil, err - } - return state, nil -} - func (a *api) GetState(ctx context.Context, in *runtimev1pb.GetStateRequest) (*runtimev1pb.GetStateResponse, error) { - store, err := a.getStateStore(in.StoreName) + store, err := a.UniversalAPI.GetStateStore(in.StoreName) if err != nil { // Error has already been logged return &runtimev1pb.GetStateResponse{}, err @@ -614,7 +597,7 @@ func (a *api) GetState(ctx context.Context, in *runtimev1pb.GetStateRequest) (*r start := time.Now() policyRunner := resiliency.NewRunner[*state.GetResponse](ctx, - a.resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), + a.UniversalAPI.Resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), ) getResponse, err := policyRunner(func(ctx context.Context) (*state.GetResponse, error) { return store.Get(ctx, req) @@ -625,7 +608,7 @@ func (a *api) GetState(ctx context.Context, in *runtimev1pb.GetStateRequest) (*r if err != nil { err = status.Errorf(codes.Internal, messages.ErrStateGet, in.Key, in.StoreName, err.Error()) - apiServerLogger.Debug(err) + a.UniversalAPI.Logger.Debug(err) return &runtimev1pb.GetStateResponse{}, err } @@ -636,7 +619,7 @@ func (a *api) GetState(ctx context.Context, in *runtimev1pb.GetStateRequest) (*r val, err := encryption.TryDecryptValue(in.StoreName, getResponse.Data) if err != nil { err = status.Errorf(codes.Internal, messages.ErrStateGet, in.Key, in.StoreName, err.Error()) - apiServerLogger.Debug(err) + a.UniversalAPI.Logger.Debug(err) return &runtimev1pb.GetStateResponse{}, err } @@ -655,7 +638,7 @@ func (a *api) GetState(ctx context.Context, in *runtimev1pb.GetStateRequest) (*r func (a *api) SaveState(ctx context.Context, in *runtimev1pb.SaveStateRequest) (*emptypb.Empty, error) { empty := &emptypb.Empty{} - store, err := a.getStateStore(in.StoreName) + store, err := a.UniversalAPI.GetStateStore(in.StoreName) if err != nil { // Error has already been logged return empty, err @@ -699,7 +682,7 @@ func (a *api) SaveState(ctx context.Context, in *runtimev1pb.SaveStateRequest) ( if encryption.EncryptedStateStore(in.StoreName) { val, encErr := encryption.TryEncryptValue(in.StoreName, s.Value) if encErr != nil { - apiServerLogger.Debug(encErr) + a.UniversalAPI.Logger.Debug(encErr) return empty, encErr } @@ -710,23 +693,19 @@ func (a *api) SaveState(ctx context.Context, in *runtimev1pb.SaveStateRequest) ( } start := time.Now() - policyRunner := resiliency.NewRunner[struct{}](ctx, - a.resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), + err = stateLoader.PerformBulkStoreOperation(ctx, reqs, + a.UniversalAPI.Resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), + state.BulkStoreOpts{}, + store.Set, + store.BulkSet, ) - _, err = policyRunner(func(ctx context.Context) (struct{}, error) { - // If there's a single request, perform it in non-bulk - if len(reqs) == 1 { - return struct{}{}, store.Set(ctx, &reqs[0]) - } - return struct{}{}, store.BulkSet(ctx, reqs) - }) elapsed := diag.ElapsedSince(start) diag.DefaultComponentMonitoring.StateInvoked(ctx, in.StoreName, diag.Set, err == nil, elapsed) if err != nil { err = a.stateErrorResponse(err, messages.ErrStateSave, in.StoreName, err.Error()) - apiServerLogger.Debug(err) + a.UniversalAPI.Logger.Debug(err) return empty, err } return empty, nil @@ -734,15 +713,14 @@ func (a *api) SaveState(ctx context.Context, in *runtimev1pb.SaveStateRequest) ( // stateErrorResponse takes a state store error, format and args and returns a status code encoded gRPC error. func (a *api) stateErrorResponse(err error, format string, args ...interface{}) error { - e, ok := err.(*state.ETagError) - if !ok { - return status.Errorf(codes.Internal, format, args...) - } - switch e.Kind() { - case state.ETagMismatch: - return status.Errorf(codes.Aborted, format, args...) - case state.ETagInvalid: - return status.Errorf(codes.InvalidArgument, format, args...) + var etagErr *state.ETagError + if errors.As(err, &etagErr) { + switch etagErr.Kind() { + case state.ETagMismatch: + return status.Errorf(codes.Aborted, format, args...) + case state.ETagInvalid: + return status.Errorf(codes.InvalidArgument, format, args...) + } } return status.Errorf(codes.Internal, format, args...) @@ -751,7 +729,7 @@ func (a *api) stateErrorResponse(err error, format string, args ...interface{}) func (a *api) DeleteState(ctx context.Context, in *runtimev1pb.DeleteStateRequest) (*emptypb.Empty, error) { empty := &emptypb.Empty{} - store, err := a.getStateStore(in.StoreName) + store, err := a.UniversalAPI.GetStateStore(in.StoreName) if err != nil { // Error has already been logged return empty, err @@ -777,7 +755,7 @@ func (a *api) DeleteState(ctx context.Context, in *runtimev1pb.DeleteStateReques start := time.Now() policyRunner := resiliency.NewRunner[any](ctx, - a.resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), + a.UniversalAPI.Resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), ) _, err = policyRunner(func(ctx context.Context) (any, error) { return nil, store.Delete(ctx, &req) @@ -788,7 +766,7 @@ func (a *api) DeleteState(ctx context.Context, in *runtimev1pb.DeleteStateReques if err != nil { err = a.stateErrorResponse(err, messages.ErrStateDelete, in.Key, err.Error()) - apiServerLogger.Debug(err) + a.UniversalAPI.Logger.Debug(err) return empty, err } return empty, nil @@ -797,14 +775,14 @@ func (a *api) DeleteState(ctx context.Context, in *runtimev1pb.DeleteStateReques func (a *api) DeleteBulkState(ctx context.Context, in *runtimev1pb.DeleteBulkStateRequest) (*emptypb.Empty, error) { empty := &emptypb.Empty{} - store, err := a.getStateStore(in.StoreName) + store, err := a.UniversalAPI.GetStateStore(in.StoreName) if err != nil { // Error has already been logged return empty, err } - reqs := make([]state.DeleteRequest, 0, len(in.States)) - for _, item := range in.States { + reqs := make([]state.DeleteRequest, len(in.States)) + for i, item := range in.States { key, err1 := stateLoader.GetModifiedStateKey(item.Key, in.StoreName, a.UniversalAPI.AppID) if err1 != nil { return empty, err1 @@ -822,24 +800,26 @@ func (a *api) DeleteBulkState(ctx context.Context, in *runtimev1pb.DeleteBulkSta Consistency: stateConsistencyToString(item.Options.Consistency), } } - reqs = append(reqs, req) + reqs[i] = req } start := time.Now() - policyRunner := resiliency.NewRunner[any](ctx, - a.resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), + err = stateLoader.PerformBulkStoreOperation(ctx, reqs, + a.UniversalAPI.Resiliency.ComponentOutboundPolicy(in.StoreName, resiliency.Statestore), + state.BulkStoreOpts{}, + store.Delete, + store.BulkDelete, ) - _, err = policyRunner(func(ctx context.Context) (any, error) { - return nil, store.BulkDelete(ctx, reqs) - }) elapsed := diag.ElapsedSince(start) diag.DefaultComponentMonitoring.StateInvoked(ctx, in.StoreName, diag.BulkDelete, err == nil, elapsed) if err != nil { - apiServerLogger.Debug(err) + err = a.stateErrorResponse(err, messages.ErrStateDeleteBulk, in.StoreName, err.Error()) + a.UniversalAPI.Logger.Debug(err) return empty, err } + return empty, nil } @@ -851,7 +831,7 @@ func extractEtag(req *commonv1pb.StateItem) (bool, string) { } func (a *api) ExecuteStateTransaction(ctx context.Context, in *runtimev1pb.ExecuteStateTransactionRequest) (*emptypb.Empty, error) { - store, storeErr := a.getStateStore(in.StoreName) + store, storeErr := a.UniversalAPI.GetStateStore(in.StoreName) if storeErr != nil { // Error has already been logged return &emptypb.Empty{}, storeErr diff --git a/pkg/grpc/api_test.go b/pkg/grpc/api_test.go index f378011ace7..2a7deeba51c 100644 --- a/pkg/grpc/api_test.go +++ b/pkg/grpc/api_test.go @@ -71,6 +71,8 @@ import ( const ( goodStoreKey = "fakeAPI||good-key" errorStoreKey = "fakeAPI||error-key" + etagMismatchKey = "fakeAPI||etag-mismatch" + etagInvalidKey = "fakeAPI||etag-invalid" goodKey = "good-key" goodKey2 = "good-key2" mockSubscribeID = "mockId" @@ -982,8 +984,10 @@ func TestGetBulkSecret(t *testing.T) { func TestGetStateWhenStoreNotConfigured(t *testing.T) { server, lis := startDaprAPIServer(&api{ UniversalAPI: &universalapi.UniversalAPI{ - AppID: "fakeAPI", - CompStore: compstore.New(), + AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), + CompStore: compstore.New(), + Resiliency: resiliency.New(nil), }, resiliency: resiliency.New(nil), }, "") @@ -998,6 +1002,9 @@ func TestGetStateWhenStoreNotConfigured(t *testing.T) { } func TestSaveState(t *testing.T) { + etagMismatchErr := state.NewETagError(state.ETagMismatch, errors.New("simulated")) + etagInvalidErr := state.NewETagError(state.ETagInvalid, errors.New("simulated")) + fakeStore := &daprt.MockStateStore{} fakeStore.On("Set", mock.MatchedBy(matchContextInterface), @@ -1011,6 +1018,49 @@ func TestSaveState(t *testing.T) { return req.Key == errorStoreKey }), ).Return(errors.New("failed to save state with error-key")) + fakeStore.On("Set", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req *state.SetRequest) bool { + return req.Key == etagMismatchKey + }), + ).Return(etagMismatchErr) + fakeStore.On("Set", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req *state.SetRequest) bool { + return req.Key == etagInvalidKey + }), + ).Return(etagInvalidErr) + fakeStore.On("BulkSet", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req []state.SetRequest) bool { + return len(req) == 2 && + req[0].Key == goodStoreKey && + req[1].Key == goodStoreKey+"2" + }), + mock.Anything, + ).Return(nil) + fakeStore.On("BulkSet", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req []state.SetRequest) bool { + return len(req) == 2 && + req[0].Key == goodStoreKey && + req[1].Key == etagMismatchKey + }), + mock.Anything, + ).Return(errors.Join(state.NewBulkStoreError(etagMismatchKey, etagMismatchErr))) + fakeStore.On("BulkSet", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req []state.SetRequest) bool { + return len(req) == 3 && + req[0].Key == goodStoreKey && + req[1].Key == etagMismatchKey && + req[2].Key == etagInvalidKey + }), + mock.Anything, + ).Return(errors.Join( + state.NewBulkStoreError(etagMismatchKey, etagMismatchErr), + state.NewBulkStoreError(etagInvalidKey, etagInvalidErr), + )) compStore := compstore.New() compStore.AddStateStore("store1", fakeStore) @@ -1018,10 +1068,11 @@ func TestSaveState(t *testing.T) { // Setup dapr api server fakeAPI := &api{ UniversalAPI: &universalapi.UniversalAPI{ - AppID: "fakeAPI", - CompStore: compStore, + AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), + CompStore: compStore, + Resiliency: resiliency.New(nil), }, - resiliency: resiliency.New(nil), } server, lis := startDaprAPIServer(fakeAPI, "") defer server.Stop() @@ -1033,58 +1084,123 @@ func TestSaveState(t *testing.T) { client := runtimev1pb.NewDaprClient(clientConn) testCases := []struct { - testName string - storeName string - key string - value string - errorExcepted bool - expectedError codes.Code + testName string + storeName string + states []*commonv1pb.StateItem + expectedCode codes.Code }{ { - testName: "save state", - storeName: "store1", - key: goodKey, - value: "value", - errorExcepted: false, - expectedError: codes.OK, + testName: "save state with non-existing store", + storeName: "store2", + expectedCode: codes.InvalidArgument, }, { - testName: "save state with non-existing store", - storeName: "store2", - key: goodKey, - value: "value", - errorExcepted: true, - expectedError: codes.InvalidArgument, + testName: "single - save state", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: goodKey, + Value: []byte("value"), + }, + }, + expectedCode: codes.OK, }, { - testName: "save state but error occurs", - storeName: "store1", - key: "error-key", - value: "value", - errorExcepted: true, - expectedError: codes.Internal, + testName: "single - save state but error occurs", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: "error-key", + Value: []byte("value"), + }, + }, + expectedCode: codes.Internal, + }, + { + testName: "single - save state with etag mismatch", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: "etag-mismatch", + Value: []byte("value"), + }, + }, + expectedCode: codes.Aborted, + }, + { + testName: "single - save state with etag invalid", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: "etag-invalid", + Value: []byte("value"), + }, + }, + expectedCode: codes.InvalidArgument, + }, + { + testName: "multi - save state", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: goodKey, + Value: []byte("value"), + }, + { + Key: goodKey + "2", + Value: []byte("value"), + }, + }, + expectedCode: codes.OK, + }, + { + testName: "multi - save state with etag mismatch", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: goodKey, + Value: []byte("value"), + }, + { + Key: "etag-mismatch", + Value: []byte("value"), + }, + }, + expectedCode: codes.Aborted, + }, + { + testName: "multi - save state with etag mismatch and etag invalid", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: goodKey, + Value: []byte("value"), + }, + { + Key: "etag-mismatch", + Value: []byte("value"), + }, + { + Key: "etag-invalid", + Value: []byte("value"), + }, + }, + expectedCode: codes.InvalidArgument, }, } // test and assert for _, tt := range testCases { t.Run(tt.testName, func(t *testing.T) { - req := &runtimev1pb.SaveStateRequest{ + _, err := client.SaveState(context.Background(), &runtimev1pb.SaveStateRequest{ StoreName: tt.storeName, - States: []*commonv1pb.StateItem{ - { - Key: tt.key, - Value: []byte(tt.value), - }, - }, - } - - _, err := client.SaveState(context.Background(), req) - if !tt.errorExcepted { - assert.NoError(t, err, "Expected no error") + States: tt.states, + }) + if tt.expectedCode == codes.OK { + require.NoError(t, err) } else { - assert.Error(t, err, "Expected error") - assert.Equal(t, tt.expectedError, status.Code(err)) + require.Error(t, err) + assert.Equal(t, tt.expectedCode, status.Code(err)) } }) } @@ -1112,10 +1228,11 @@ func TestGetState(t *testing.T) { // Setup dapr api server fakeAPI := &api{ UniversalAPI: &universalapi.UniversalAPI{ - AppID: "fakeAPI", - CompStore: compStore, + AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), + CompStore: compStore, + Resiliency: resiliency.New(nil), }, - resiliency: resiliency.New(nil), } server, lis := startDaprAPIServer(fakeAPI, "") defer server.Stop() @@ -1852,10 +1969,11 @@ func TestGetBulkState(t *testing.T) { // Setup dapr api server fakeAPI := &api{ UniversalAPI: &universalapi.UniversalAPI{ - AppID: "fakeAPI", - CompStore: compStore, + AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), + CompStore: compStore, + Resiliency: resiliency.New(nil), }, - resiliency: resiliency.New(nil), } server, lis := startDaprAPIServer(fakeAPI, "") defer server.Stop() @@ -1955,17 +2073,34 @@ func TestGetBulkState(t *testing.T) { } func TestDeleteState(t *testing.T) { + etagMismatchErr := state.NewETagError(state.ETagMismatch, errors.New("simulated")) + etagInvalidErr := state.NewETagError(state.ETagInvalid, errors.New("simulated")) + fakeStore := &daprt.MockStateStore{} fakeStore.On("Delete", mock.MatchedBy(matchContextInterface), mock.MatchedBy(func(req *state.DeleteRequest) bool { return req.Key == goodStoreKey - })).Return(nil) + }), + ).Return(nil) fakeStore.On("Delete", mock.MatchedBy(matchContextInterface), mock.MatchedBy(func(req *state.DeleteRequest) bool { return req.Key == errorStoreKey - })).Return(errors.New("failed to delete state with key2")) + }), + ).Return(errors.New("failed to delete state with key2")) + fakeStore.On("Delete", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req *state.DeleteRequest) bool { + return req.Key == etagMismatchKey + }), + ).Return(etagMismatchErr) + fakeStore.On("Delete", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req *state.DeleteRequest) bool { + return req.Key == etagInvalidKey + }), + ).Return(etagInvalidErr) compStore := compstore.New() compStore.AddStateStore("store1", fakeStore) @@ -1973,10 +2108,11 @@ func TestDeleteState(t *testing.T) { // Setup dapr api server fakeAPI := &api{ UniversalAPI: &universalapi.UniversalAPI{ - AppID: "fakeAPI", - CompStore: compStore, + AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), + CompStore: compStore, + Resiliency: resiliency.New(nil), }, - resiliency: resiliency.New(nil), } server, lis := startDaprAPIServer(fakeAPI, "") defer server.Stop() @@ -1987,48 +2123,182 @@ func TestDeleteState(t *testing.T) { client := runtimev1pb.NewDaprClient(clientConn) testCases := []struct { - testName string - storeName string - key string - errorExcepted bool - expectedError codes.Code + testName string + storeName string + key string + expectedCode codes.Code }{ { - testName: "delete state", - storeName: "store1", - key: goodKey, - errorExcepted: false, - expectedError: codes.OK, + testName: "delete state", + storeName: "store1", + key: goodKey, + expectedCode: codes.OK, }, { - testName: "delete store with non-existing store", - storeName: "no-store", - key: goodKey, - errorExcepted: true, - expectedError: codes.InvalidArgument, + testName: "delete state with non-existing store", + storeName: "no-store", + key: goodKey, + expectedCode: codes.InvalidArgument, }, { - testName: "delete store with key but error occurs", - storeName: "store1", - key: "error-key", - errorExcepted: true, - expectedError: codes.Internal, + testName: "delete state with key but error occurs", + storeName: "store1", + key: "error-key", + expectedCode: codes.Internal, + }, + { + testName: "delete state with etag mismatch", + storeName: "store1", + key: "etag-mismatch", + expectedCode: codes.Aborted, + }, + { + testName: "delete state with etag invalid", + storeName: "store1", + key: "etag-invalid", + expectedCode: codes.InvalidArgument, }, } for _, tt := range testCases { t.Run(tt.testName, func(t *testing.T) { - req := &runtimev1pb.DeleteStateRequest{ + _, err := client.DeleteState(context.Background(), &runtimev1pb.DeleteStateRequest{ StoreName: tt.storeName, Key: tt.key, + }) + if tt.expectedCode == codes.OK { + require.NoError(t, err) + } else { + require.Error(t, err) + assert.Equal(t, tt.expectedCode, status.Code(err)) } + }) + } +} - _, err := client.DeleteState(context.Background(), req) - if !tt.errorExcepted { - assert.NoError(t, err, "Expected no error") +func TestDeleteBulkState(t *testing.T) { + etagMismatchErr := state.NewETagError(state.ETagMismatch, errors.New("simulated")) + etagInvalidErr := state.NewETagError(state.ETagInvalid, errors.New("simulated")) + + fakeStore := &daprt.MockStateStore{} + fakeStore.On("BulkDelete", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req []state.DeleteRequest) bool { + return len(req) == 2 && + req[0].Key == goodStoreKey && + req[1].Key == goodStoreKey+"2" + }), + mock.Anything, + ).Return(nil) + fakeStore.On("BulkDelete", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req []state.DeleteRequest) bool { + return len(req) == 2 && + req[0].Key == goodStoreKey && + req[1].Key == etagMismatchKey + }), + mock.Anything, + ).Return(errors.Join(state.NewBulkStoreError(etagMismatchKey, etagMismatchErr))) + fakeStore.On("BulkDelete", + mock.MatchedBy(matchContextInterface), + mock.MatchedBy(func(req []state.DeleteRequest) bool { + return len(req) == 3 && + req[0].Key == goodStoreKey && + req[1].Key == etagMismatchKey && + req[2].Key == etagInvalidKey + }), + mock.Anything, + ).Return(errors.Join( + state.NewBulkStoreError(etagMismatchKey, etagMismatchErr), + state.NewBulkStoreError(etagInvalidKey, etagInvalidErr), + )) + + compStore := compstore.New() + compStore.AddStateStore("store1", fakeStore) + + // Setup dapr api server + fakeAPI := &api{ + UniversalAPI: &universalapi.UniversalAPI{ + AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), + CompStore: compStore, + Resiliency: resiliency.New(nil), + }, + } + server, lis := startDaprAPIServer(fakeAPI, "") + defer server.Stop() + + clientConn := createTestClient(lis) + defer clientConn.Close() + + client := runtimev1pb.NewDaprClient(clientConn) + + testCases := []struct { + testName string + storeName string + states []*commonv1pb.StateItem + expectedCode codes.Code + }{ + { + testName: "delete state with non-existing store", + storeName: "store2", + expectedCode: codes.InvalidArgument, + }, + { + testName: "delete state", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: goodKey, + }, + { + Key: goodKey + "2", + }, + }, + expectedCode: codes.OK, + }, + { + testName: "delete state with etag mismatch", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: goodKey, + }, + { + Key: "etag-mismatch", + }, + }, + expectedCode: codes.Aborted, + }, + { + testName: "delete state with etag mismatch and etag invalid", + storeName: "store1", + states: []*commonv1pb.StateItem{ + { + Key: goodKey, + }, + { + Key: "etag-mismatch", + }, + { + Key: "etag-invalid", + }, + }, + expectedCode: codes.InvalidArgument, + }, + } + + for _, tt := range testCases { + t.Run(tt.testName, func(t *testing.T) { + _, err := client.DeleteBulkState(context.Background(), &runtimev1pb.DeleteBulkStateRequest{ + StoreName: tt.storeName, + States: tt.states, + }) + if tt.expectedCode == codes.OK { + require.NoError(t, err) } else { - assert.Error(t, err, "Expected error") - assert.Equal(t, tt.expectedError, status.Code(err)) + require.Error(t, err) + assert.Equal(t, tt.expectedCode, status.Code(err)) } }) } @@ -2371,6 +2641,7 @@ func TestTransactionStateStoreNotConfigured(t *testing.T) { server, lis := startDaprAPIServer(&api{ UniversalAPI: &universalapi.UniversalAPI{ AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), CompStore: compstore.New(), }, }, "") @@ -2390,6 +2661,7 @@ func TestTransactionStateStoreNotImplemented(t *testing.T) { server, lis := startDaprAPIServer(&api{ UniversalAPI: &universalapi.UniversalAPI{ AppID: "fakeAPI", + Logger: logger.NewLogger("grpc.api.test"), CompStore: compStore, }, }, "") @@ -2436,6 +2708,7 @@ func TestExecuteStateTransaction(t *testing.T) { // Setup dapr api server fakeAPI := &api{ UniversalAPI: &universalapi.UniversalAPI{ + Logger: logger.NewLogger("grpc.api.test"), AppID: "fakeAPI", CompStore: compStore, }, diff --git a/pkg/grpc/universalapi/api_state_query.go b/pkg/grpc/universalapi/api_state_query.go index d662f4d2f58..f3d786f6a48 100644 --- a/pkg/grpc/universalapi/api_state_query.go +++ b/pkg/grpc/universalapi/api_state_query.go @@ -27,7 +27,7 @@ import ( "github.com/dapr/dapr/pkg/resiliency" ) -func (a *UniversalAPI) getStateStore(name string) (state.Store, error) { +func (a *UniversalAPI) GetStateStore(name string) (state.Store, error) { if a.CompStore.StateStoresLen() == 0 { err := messages.ErrStateStoresNotConfigured a.Logger.Debug(err) @@ -45,7 +45,7 @@ func (a *UniversalAPI) getStateStore(name string) (state.Store, error) { } func (a *UniversalAPI) QueryStateAlpha1(ctx context.Context, in *runtimev1pb.QueryStateRequest) (*runtimev1pb.QueryStateResponse, error) { - store, err := a.getStateStore(in.StoreName) + store, err := a.GetStateStore(in.StoreName) if err != nil { // Error has already been logged return nil, err diff --git a/pkg/http/api.go b/pkg/http/api.go index 114cd512b14..63db766f579 100644 --- a/pkg/http/api.go +++ b/pkg/http/api.go @@ -1175,23 +1175,17 @@ func (a *api) onPostState(reqCtx *fasthttp.RequestCtx) { } start := time.Now() - policyRunner := resiliency.NewRunner[struct{}](reqCtx, + err = stateLoader.PerformBulkStoreOperation(reqCtx, reqs, a.resiliency.ComponentOutboundPolicy(storeName, resiliency.Statestore), + state.BulkStoreOpts{}, + store.Set, + store.BulkSet, ) - _, err = policyRunner(func(ctx context.Context) (struct{}, error) { - // If there's a single request, perform it in non-bulk - if len(reqs) == 1 { - return struct{}{}, store.Set(ctx, &reqs[0]) - } - return struct{}{}, store.BulkSet(ctx, reqs) - }) elapsed := diag.ElapsedSince(start) diag.DefaultComponentMonitoring.StateInvoked(reqCtx, storeName, diag.Set, err == nil, elapsed) if err != nil { - storeName := a.getStateStoreName(reqCtx) - statusCode, errMsg, resp := a.stateErrorResponse(err, "ERR_STATE_SAVE") resp.Message = fmt.Sprintf(messages.ErrStateSave, storeName, errMsg) @@ -1205,10 +1199,7 @@ func (a *api) onPostState(reqCtx *fasthttp.RequestCtx) { // stateErrorResponse takes a state store error and returns a corresponding status code, error message and modified user error. func (a *api) stateErrorResponse(err error, errorCode string) (int, string, ErrorResponse) { - var message string - var code int - var etag bool - etag, code, message = a.etagError(err) + etag, code, message := a.etagError(err) r := ErrorResponse{ ErrorCode: errorCode, @@ -1224,17 +1215,15 @@ func (a *api) stateErrorResponse(err error, errorCode string) (int, string, Erro // etagError checks if the error from the state store is an etag error and returns a bool for indication, // an status code and an error message. func (a *api) etagError(err error) (bool, int, string) { - e, ok := err.(*state.ETagError) - if !ok { - return false, -1, "" - } - switch e.Kind() { - case state.ETagMismatch: - return true, fasthttp.StatusConflict, e.Error() - case state.ETagInvalid: - return true, fasthttp.StatusBadRequest, e.Error() + var etagErr *state.ETagError + if errors.As(err, &etagErr) { + switch etagErr.Kind() { + case state.ETagMismatch: + return true, fasthttp.StatusConflict, etagErr.Error() + case state.ETagInvalid: + return true, fasthttp.StatusBadRequest, etagErr.Error() + } } - return false, -1, "" } diff --git a/pkg/http/api_test.go b/pkg/http/api_test.go index a1a863ddb08..46313c2e52f 100644 --- a/pkg/http/api_test.go +++ b/pkg/http/api_test.go @@ -4475,7 +4475,62 @@ func TestV1StateEndpoints(t *testing.T) { // act resp := fakeServer.DoRequest("POST", apiPath, b, nil) // assert - assert.Equal(t, 500, resp.StatusCode, "updating existing key with wrong etag should fail") + assert.Equal(t, 409, resp.StatusCode, "updating existing key with wrong etag should fail") + }) + + t.Run("Update bulk state - No ETag", func(t *testing.T) { + apiPath := fmt.Sprintf("v1.0/state/%s", storeName) + request := []state.SetRequest{ + {Key: "good-key"}, + {Key: "good-key2"}, + } + b, _ := json.Marshal(request) + // act + resp := fakeServer.DoRequest("POST", apiPath, b, nil) + // assert + assert.Equal(t, 204, resp.StatusCode) + assert.Equal(t, "", string(resp.RawBody)) + }) + + t.Run("Update bulk state - State Error", func(t *testing.T) { + apiPath := fmt.Sprintf("v1.0/state/%s", storeName) + request := []state.SetRequest{ + {Key: "good-key"}, + {Key: "error-key"}, + } + b, _ := json.Marshal(request) + // act + resp := fakeServer.DoRequest("POST", apiPath, b, nil) + // assert + assert.Equal(t, 500, resp.StatusCode) + assert.Equal(t, "ERR_STATE_SAVE", resp.ErrorBody["errorCode"]) + }) + + t.Run("Update bulk state - Matching ETag", func(t *testing.T) { + apiPath := fmt.Sprintf("v1.0/state/%s", storeName) + request := []state.SetRequest{ + {Key: "good-key", ETag: &etag}, + {Key: "good-key2", ETag: &etag}, + } + b, _ := json.Marshal(request) + // act + resp := fakeServer.DoRequest("POST", apiPath, b, nil) + // assert + assert.Equal(t, 204, resp.StatusCode) + assert.Equal(t, "", string(resp.RawBody)) + }) + + t.Run("Update bulk state - One has invalid ETag", func(t *testing.T) { + apiPath := fmt.Sprintf("v1.0/state/%s", storeName) + request := []state.SetRequest{ + {Key: "good-key", ETag: &etag}, + {Key: "good-key2", ETag: ptr.Of("BAD ETAG")}, + } + b, _ := json.Marshal(request) + // act + resp := fakeServer.DoRequest("POST", apiPath, b, nil) + // assert + assert.Equal(t, 409, resp.StatusCode) }) t.Run("Delete state - No ETag", func(t *testing.T) { @@ -4501,7 +4556,7 @@ func TestV1StateEndpoints(t *testing.T) { // act resp := fakeServer.DoRequest("DELETE", apiPath, nil, nil, "If-Match", "BAD ETAG") // assert - assert.Equal(t, 500, resp.StatusCode, "updating existing key with wrong etag should fail") + assert.Equal(t, 409, resp.StatusCode, "updating existing key with wrong etag should fail") }) t.Run("Bulk state get - Empty request", func(t *testing.T) { @@ -4925,7 +4980,7 @@ func (c fakeStateStore) Ping() error { func (c fakeStateStore) Delete(ctx context.Context, req *state.DeleteRequest) error { if req.Key == "good-key" { if req.ETag != nil && *req.ETag != "`~!@#$%^&*()_+-={}[]|\\:\";'<>?,./'" { - return errors.New("ETag mismatch") + return state.NewETagError(state.ETagMismatch, errors.New("ETag mismatch")) } return nil } @@ -4957,9 +5012,9 @@ func (c fakeStateStore) Features() []state.Feature { } func (c fakeStateStore) Set(ctx context.Context, req *state.SetRequest) error { - if req.Key == "good-key" { + if req.Key == "good-key" || req.Key == "good-key2" { if req.ETag != nil && *req.ETag != "`~!@#$%^&*()_+-={}[]|\\:\";'<>?,./'" { - return errors.New("ETag mismatch") + return state.NewETagError(state.ETagMismatch, errors.New("ETag mismatch")) } return nil } diff --git a/pkg/messages/predefined.go b/pkg/messages/predefined.go index 88d851af73e..c7fc9da2408 100644 --- a/pkg/messages/predefined.go +++ b/pkg/messages/predefined.go @@ -26,9 +26,10 @@ const ( ErrMalformedRequestData = "can't serialize request data field: %s" // State. - ErrStateGet = "fail to get %s from state store %s: %s" - ErrStateDelete = "failed deleting state with key %s: %s" - ErrStateSave = "failed saving state in state store %s: %s" + ErrStateGet = "fail to get %s from state store %s: %s" + ErrStateDelete = "failed deleting state with key %s: %s" + ErrStateSave = "failed saving state in state store %s: %s" + ErrStateDeleteBulk = "failed deleting state in state store %s: %s" // StateTransaction. ErrStateStoreNotSupported = "state store %s doesn't support transaction" diff --git a/pkg/proto/components/v1/state.pb.go b/pkg/proto/components/v1/state.pb.go index 55f5a10ddd5..032d1616d95 100644 --- a/pkg/proto/components/v1/state.pb.go +++ b/pkg/proto/components/v1/state.pb.go @@ -1333,18 +1333,66 @@ func (*SetResponse) Descriptor() ([]byte, []int) { return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{18} } +type BulkDeleteRequestOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Parallelism int64 `protobuf:"varint,1,opt,name=parallelism,proto3" json:"parallelism,omitempty"` +} + +func (x *BulkDeleteRequestOptions) Reset() { + *x = BulkDeleteRequestOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BulkDeleteRequestOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BulkDeleteRequestOptions) ProtoMessage() {} + +func (x *BulkDeleteRequestOptions) ProtoReflect() protoreflect.Message { + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BulkDeleteRequestOptions.ProtoReflect.Descriptor instead. +func (*BulkDeleteRequestOptions) Descriptor() ([]byte, []int) { + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{19} +} + +func (x *BulkDeleteRequestOptions) GetParallelism() int64 { + if x != nil { + return x.Parallelism + } + return 0 +} + type BulkDeleteRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Items []*DeleteRequest `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Items []*DeleteRequest `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Options *BulkDeleteRequestOptions `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` } func (x *BulkDeleteRequest) Reset() { *x = BulkDeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[19] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1357,7 +1405,7 @@ func (x *BulkDeleteRequest) String() string { func (*BulkDeleteRequest) ProtoMessage() {} func (x *BulkDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[19] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1370,7 +1418,7 @@ func (x *BulkDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkDeleteRequest.ProtoReflect.Descriptor instead. func (*BulkDeleteRequest) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{19} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{20} } func (x *BulkDeleteRequest) GetItems() []*DeleteRequest { @@ -1380,6 +1428,13 @@ func (x *BulkDeleteRequest) GetItems() []*DeleteRequest { return nil } +func (x *BulkDeleteRequest) GetOptions() *BulkDeleteRequestOptions { + if x != nil { + return x.Options + } + return nil +} + // reserved for future-proof extensibility type BulkDeleteResponse struct { state protoimpl.MessageState @@ -1390,7 +1445,7 @@ type BulkDeleteResponse struct { func (x *BulkDeleteResponse) Reset() { *x = BulkDeleteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[20] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1403,7 +1458,7 @@ func (x *BulkDeleteResponse) String() string { func (*BulkDeleteResponse) ProtoMessage() {} func (x *BulkDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[20] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1416,7 +1471,7 @@ func (x *BulkDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkDeleteResponse.ProtoReflect.Descriptor instead. func (*BulkDeleteResponse) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{20} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{21} } type BulkGetRequestOptions struct { @@ -1430,7 +1485,7 @@ type BulkGetRequestOptions struct { func (x *BulkGetRequestOptions) Reset() { *x = BulkGetRequestOptions{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[21] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1443,7 +1498,7 @@ func (x *BulkGetRequestOptions) String() string { func (*BulkGetRequestOptions) ProtoMessage() {} func (x *BulkGetRequestOptions) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[21] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1456,7 +1511,7 @@ func (x *BulkGetRequestOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkGetRequestOptions.ProtoReflect.Descriptor instead. func (*BulkGetRequestOptions) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{21} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{22} } func (x *BulkGetRequestOptions) GetParallelism() int64 { @@ -1478,7 +1533,7 @@ type BulkGetRequest struct { func (x *BulkGetRequest) Reset() { *x = BulkGetRequest{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[22] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1491,7 +1546,7 @@ func (x *BulkGetRequest) String() string { func (*BulkGetRequest) ProtoMessage() {} func (x *BulkGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[22] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1504,7 +1559,7 @@ func (x *BulkGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkGetRequest.ProtoReflect.Descriptor instead. func (*BulkGetRequest) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{22} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{23} } func (x *BulkGetRequest) GetItems() []*GetRequest { @@ -1543,7 +1598,7 @@ type BulkStateItem struct { func (x *BulkStateItem) Reset() { *x = BulkStateItem{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[23] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1556,7 +1611,7 @@ func (x *BulkStateItem) String() string { func (*BulkStateItem) ProtoMessage() {} func (x *BulkStateItem) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[23] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1569,7 +1624,7 @@ func (x *BulkStateItem) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkStateItem.ProtoReflect.Descriptor instead. func (*BulkStateItem) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{23} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{24} } func (x *BulkStateItem) GetKey() string { @@ -1625,7 +1680,7 @@ type BulkGetResponse struct { func (x *BulkGetResponse) Reset() { *x = BulkGetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[24] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1638,7 +1693,7 @@ func (x *BulkGetResponse) String() string { func (*BulkGetResponse) ProtoMessage() {} func (x *BulkGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[24] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1651,7 +1706,7 @@ func (x *BulkGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkGetResponse.ProtoReflect.Descriptor instead. func (*BulkGetResponse) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{24} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{25} } func (x *BulkGetResponse) GetItems() []*BulkStateItem { @@ -1661,18 +1716,66 @@ func (x *BulkGetResponse) GetItems() []*BulkStateItem { return nil } +type BulkSetRequestOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Parallelism int64 `protobuf:"varint,1,opt,name=parallelism,proto3" json:"parallelism,omitempty"` +} + +func (x *BulkSetRequestOptions) Reset() { + *x = BulkSetRequestOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BulkSetRequestOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BulkSetRequestOptions) ProtoMessage() {} + +func (x *BulkSetRequestOptions) ProtoReflect() protoreflect.Message { + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BulkSetRequestOptions.ProtoReflect.Descriptor instead. +func (*BulkSetRequestOptions) Descriptor() ([]byte, []int) { + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{26} +} + +func (x *BulkSetRequestOptions) GetParallelism() int64 { + if x != nil { + return x.Parallelism + } + return 0 +} + type BulkSetRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Items []*SetRequest `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Items []*SetRequest `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Options *BulkSetRequestOptions `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` } func (x *BulkSetRequest) Reset() { *x = BulkSetRequest{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[25] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1685,7 +1788,7 @@ func (x *BulkSetRequest) String() string { func (*BulkSetRequest) ProtoMessage() {} func (x *BulkSetRequest) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[25] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1698,7 +1801,7 @@ func (x *BulkSetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkSetRequest.ProtoReflect.Descriptor instead. func (*BulkSetRequest) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{25} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{27} } func (x *BulkSetRequest) GetItems() []*SetRequest { @@ -1708,6 +1811,13 @@ func (x *BulkSetRequest) GetItems() []*SetRequest { return nil } +func (x *BulkSetRequest) GetOptions() *BulkSetRequestOptions { + if x != nil { + return x.Options + } + return nil +} + // reserved for future-proof extensibility type BulkSetResponse struct { state protoimpl.MessageState @@ -1718,7 +1828,7 @@ type BulkSetResponse struct { func (x *BulkSetResponse) Reset() { *x = BulkSetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[26] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1731,7 +1841,7 @@ func (x *BulkSetResponse) String() string { func (*BulkSetResponse) ProtoMessage() {} func (x *BulkSetResponse) ProtoReflect() protoreflect.Message { - mi := &file_dapr_proto_components_v1_state_proto_msgTypes[26] + mi := &file_dapr_proto_components_v1_state_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1744,7 +1854,7 @@ func (x *BulkSetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BulkSetResponse.ProtoReflect.Descriptor instead. func (*BulkSetResponse) Descriptor() ([]byte, []int) { - return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{26} + return file_dapr_proto_components_v1_state_proto_rawDescGZIP(), []int{28} } var File_dapr_proto_components_v1_state_proto protoreflect.FileDescriptor @@ -1964,132 +2074,149 @@ var file_dapr_proto_components_v1_state_proto_rawDesc = []byte{ 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x0a, 0x11, 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x64, 0x61, 0x70, 0x72, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x0a, 0x18, 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, + 0x69, 0x73, 0x6d, 0x22, 0xa0, 0x01, 0x0a, 0x11, 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x4c, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x14, 0x0a, 0x12, 0x42, 0x75, 0x6c, - 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x39, 0x0a, 0x15, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, - 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, - 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x22, 0x97, 0x01, 0x0a, 0x0e, 0x42, - 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x64, - 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x49, 0x0a, 0x07, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x64, 0x61, 0x70, - 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xb2, 0x02, 0x0a, 0x0d, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x04, - 0x65, 0x74, 0x61, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x64, 0x61, 0x70, - 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x74, 0x61, 0x67, 0x52, 0x04, 0x65, 0x74, 0x61, 0x67, - 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x51, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x14, 0x0a, 0x12, 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x0a, 0x15, + 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, + 0x6c, 0x69, 0x73, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, + 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x22, 0x97, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6c, 0x6b, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x64, 0x61, 0x70, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x49, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0xb2, 0x02, 0x0a, 0x0d, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x49, + 0x74, 0x65, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x04, 0x65, 0x74, 0x61, + 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x45, 0x74, 0x61, 0x67, 0x52, 0x04, 0x65, 0x74, 0x61, 0x67, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x51, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x50, 0x0a, 0x0f, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, - 0x6d, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x1a, 0x3b, 0x0a, 0x0d, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x50, 0x0a, 0x0f, 0x42, 0x75, 0x6c, - 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, - 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x64, 0x61, + 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x39, 0x0a, 0x15, 0x42, 0x75, 0x6c, 0x6b, + 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, + 0x69, 0x73, 0x6d, 0x22, 0x97, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x12, 0x49, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, + 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x11, 0x0a, + 0x0f, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0x71, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x5a, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x12, 0x26, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, + 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x32, 0x92, 0x01, 0x0a, 0x17, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, + 0x77, 0x0a, 0x08, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x12, 0x33, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x4c, 0x0a, 0x0e, 0x42, - 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x64, - 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x42, 0x75, 0x6c, - 0x6b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x71, 0x0a, 0x13, - 0x51, 0x75, 0x65, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x12, 0x5a, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x26, 0x2e, 0x64, + 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x34, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, + 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xdd, 0x06, 0x0a, 0x0a, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x57, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, + 0x25, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x63, 0x0a, 0x08, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x29, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, - 0x92, 0x01, 0x0a, 0x17, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x77, 0x0a, 0x08, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x12, 0x33, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, + 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x64, - 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x32, 0xdd, 0x06, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x12, 0x57, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x25, 0x2e, 0x64, 0x61, - 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, - 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x08, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x29, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x27, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x5d, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x27, 0x2e, 0x64, 0x61, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x54, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, + 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x25, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, + 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x03, 0x53, 0x65, + 0x74, 0x12, 0x24, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, + 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x57, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x25, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x69, 0x0a, 0x0a, 0x42, 0x75, 0x6c, + 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x2b, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, - 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, - 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x12, 0x24, 0x2e, - 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, - 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x04, - 0x50, 0x69, 0x6e, 0x67, 0x12, 0x25, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x61, - 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x69, 0x0a, 0x0a, 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x2b, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2c, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x60, 0x0a, 0x07, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x12, 0x28, 0x2e, 0x64, 0x61, + 0x42, 0x75, 0x6c, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x07, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x12, + 0x28, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x64, 0x61, 0x70, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x07, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x65, + 0x74, 0x12, 0x28, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, + 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, + 0x6b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x60, 0x0a, 0x07, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, 0x12, 0x28, 0x2e, - 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, - 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x64, 0x61, 0x70, 0x72, 0x2f, 0x64, 0x61, 0x70, 0x72, 0x2f, 0x70, 0x6b, 0x67, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x65, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x61, 0x70, 0x72, 0x2f, 0x64, 0x61, 0x70, 0x72, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, + 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2105,7 +2232,7 @@ func file_dapr_proto_components_v1_state_proto_rawDescGZIP() []byte { } var file_dapr_proto_components_v1_state_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_dapr_proto_components_v1_state_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_dapr_proto_components_v1_state_proto_msgTypes = make([]protoimpl.MessageInfo, 38) var file_dapr_proto_components_v1_state_proto_goTypes = []interface{}{ (Sorting_Order)(0), // 0: dapr.proto.components.v1.Sorting.Order (StateOptions_StateConcurrency)(0), // 1: dapr.proto.components.v1.StateOptions.StateConcurrency @@ -2129,92 +2256,96 @@ var file_dapr_proto_components_v1_state_proto_goTypes = []interface{}{ (*DeleteResponse)(nil), // 19: dapr.proto.components.v1.DeleteResponse (*SetRequest)(nil), // 20: dapr.proto.components.v1.SetRequest (*SetResponse)(nil), // 21: dapr.proto.components.v1.SetResponse - (*BulkDeleteRequest)(nil), // 22: dapr.proto.components.v1.BulkDeleteRequest - (*BulkDeleteResponse)(nil), // 23: dapr.proto.components.v1.BulkDeleteResponse - (*BulkGetRequestOptions)(nil), // 24: dapr.proto.components.v1.BulkGetRequestOptions - (*BulkGetRequest)(nil), // 25: dapr.proto.components.v1.BulkGetRequest - (*BulkStateItem)(nil), // 26: dapr.proto.components.v1.BulkStateItem - (*BulkGetResponse)(nil), // 27: dapr.proto.components.v1.BulkGetResponse - (*BulkSetRequest)(nil), // 28: dapr.proto.components.v1.BulkSetRequest - (*BulkSetResponse)(nil), // 29: dapr.proto.components.v1.BulkSetResponse - nil, // 30: dapr.proto.components.v1.Query.FilterEntry - nil, // 31: dapr.proto.components.v1.QueryRequest.MetadataEntry - nil, // 32: dapr.proto.components.v1.QueryResponse.MetadataEntry - nil, // 33: dapr.proto.components.v1.TransactionalStateRequest.MetadataEntry - nil, // 34: dapr.proto.components.v1.GetRequest.MetadataEntry - nil, // 35: dapr.proto.components.v1.GetResponse.MetadataEntry - nil, // 36: dapr.proto.components.v1.DeleteRequest.MetadataEntry - nil, // 37: dapr.proto.components.v1.SetRequest.MetadataEntry - nil, // 38: dapr.proto.components.v1.BulkStateItem.MetadataEntry - (*MetadataRequest)(nil), // 39: dapr.proto.components.v1.MetadataRequest - (*anypb.Any)(nil), // 40: google.protobuf.Any - (*FeaturesRequest)(nil), // 41: dapr.proto.components.v1.FeaturesRequest - (*PingRequest)(nil), // 42: dapr.proto.components.v1.PingRequest - (*FeaturesResponse)(nil), // 43: dapr.proto.components.v1.FeaturesResponse - (*PingResponse)(nil), // 44: dapr.proto.components.v1.PingResponse + (*BulkDeleteRequestOptions)(nil), // 22: dapr.proto.components.v1.BulkDeleteRequestOptions + (*BulkDeleteRequest)(nil), // 23: dapr.proto.components.v1.BulkDeleteRequest + (*BulkDeleteResponse)(nil), // 24: dapr.proto.components.v1.BulkDeleteResponse + (*BulkGetRequestOptions)(nil), // 25: dapr.proto.components.v1.BulkGetRequestOptions + (*BulkGetRequest)(nil), // 26: dapr.proto.components.v1.BulkGetRequest + (*BulkStateItem)(nil), // 27: dapr.proto.components.v1.BulkStateItem + (*BulkGetResponse)(nil), // 28: dapr.proto.components.v1.BulkGetResponse + (*BulkSetRequestOptions)(nil), // 29: dapr.proto.components.v1.BulkSetRequestOptions + (*BulkSetRequest)(nil), // 30: dapr.proto.components.v1.BulkSetRequest + (*BulkSetResponse)(nil), // 31: dapr.proto.components.v1.BulkSetResponse + nil, // 32: dapr.proto.components.v1.Query.FilterEntry + nil, // 33: dapr.proto.components.v1.QueryRequest.MetadataEntry + nil, // 34: dapr.proto.components.v1.QueryResponse.MetadataEntry + nil, // 35: dapr.proto.components.v1.TransactionalStateRequest.MetadataEntry + nil, // 36: dapr.proto.components.v1.GetRequest.MetadataEntry + nil, // 37: dapr.proto.components.v1.GetResponse.MetadataEntry + nil, // 38: dapr.proto.components.v1.DeleteRequest.MetadataEntry + nil, // 39: dapr.proto.components.v1.SetRequest.MetadataEntry + nil, // 40: dapr.proto.components.v1.BulkStateItem.MetadataEntry + (*MetadataRequest)(nil), // 41: dapr.proto.components.v1.MetadataRequest + (*anypb.Any)(nil), // 42: google.protobuf.Any + (*FeaturesRequest)(nil), // 43: dapr.proto.components.v1.FeaturesRequest + (*PingRequest)(nil), // 44: dapr.proto.components.v1.PingRequest + (*FeaturesResponse)(nil), // 45: dapr.proto.components.v1.FeaturesResponse + (*PingResponse)(nil), // 46: dapr.proto.components.v1.PingResponse } var file_dapr_proto_components_v1_state_proto_depIdxs = []int32{ 0, // 0: dapr.proto.components.v1.Sorting.order:type_name -> dapr.proto.components.v1.Sorting.Order - 30, // 1: dapr.proto.components.v1.Query.filter:type_name -> dapr.proto.components.v1.Query.FilterEntry + 32, // 1: dapr.proto.components.v1.Query.filter:type_name -> dapr.proto.components.v1.Query.FilterEntry 3, // 2: dapr.proto.components.v1.Query.sort:type_name -> dapr.proto.components.v1.Sorting 4, // 3: dapr.proto.components.v1.Query.pagination:type_name -> dapr.proto.components.v1.Pagination 5, // 4: dapr.proto.components.v1.QueryRequest.query:type_name -> dapr.proto.components.v1.Query - 31, // 5: dapr.proto.components.v1.QueryRequest.metadata:type_name -> dapr.proto.components.v1.QueryRequest.MetadataEntry + 33, // 5: dapr.proto.components.v1.QueryRequest.metadata:type_name -> dapr.proto.components.v1.QueryRequest.MetadataEntry 12, // 6: dapr.proto.components.v1.QueryItem.etag:type_name -> dapr.proto.components.v1.Etag 7, // 7: dapr.proto.components.v1.QueryResponse.items:type_name -> dapr.proto.components.v1.QueryItem - 32, // 8: dapr.proto.components.v1.QueryResponse.metadata:type_name -> dapr.proto.components.v1.QueryResponse.MetadataEntry + 34, // 8: dapr.proto.components.v1.QueryResponse.metadata:type_name -> dapr.proto.components.v1.QueryResponse.MetadataEntry 18, // 9: dapr.proto.components.v1.TransactionalStateOperation.delete:type_name -> dapr.proto.components.v1.DeleteRequest 20, // 10: dapr.proto.components.v1.TransactionalStateOperation.set:type_name -> dapr.proto.components.v1.SetRequest 9, // 11: dapr.proto.components.v1.TransactionalStateRequest.operations:type_name -> dapr.proto.components.v1.TransactionalStateOperation - 33, // 12: dapr.proto.components.v1.TransactionalStateRequest.metadata:type_name -> dapr.proto.components.v1.TransactionalStateRequest.MetadataEntry + 35, // 12: dapr.proto.components.v1.TransactionalStateRequest.metadata:type_name -> dapr.proto.components.v1.TransactionalStateRequest.MetadataEntry 1, // 13: dapr.proto.components.v1.StateOptions.concurrency:type_name -> dapr.proto.components.v1.StateOptions.StateConcurrency 2, // 14: dapr.proto.components.v1.StateOptions.consistency:type_name -> dapr.proto.components.v1.StateOptions.StateConsistency - 39, // 15: dapr.proto.components.v1.InitRequest.metadata:type_name -> dapr.proto.components.v1.MetadataRequest - 34, // 16: dapr.proto.components.v1.GetRequest.metadata:type_name -> dapr.proto.components.v1.GetRequest.MetadataEntry + 41, // 15: dapr.proto.components.v1.InitRequest.metadata:type_name -> dapr.proto.components.v1.MetadataRequest + 36, // 16: dapr.proto.components.v1.GetRequest.metadata:type_name -> dapr.proto.components.v1.GetRequest.MetadataEntry 2, // 17: dapr.proto.components.v1.GetRequest.consistency:type_name -> dapr.proto.components.v1.StateOptions.StateConsistency 12, // 18: dapr.proto.components.v1.GetResponse.etag:type_name -> dapr.proto.components.v1.Etag - 35, // 19: dapr.proto.components.v1.GetResponse.metadata:type_name -> dapr.proto.components.v1.GetResponse.MetadataEntry + 37, // 19: dapr.proto.components.v1.GetResponse.metadata:type_name -> dapr.proto.components.v1.GetResponse.MetadataEntry 12, // 20: dapr.proto.components.v1.DeleteRequest.etag:type_name -> dapr.proto.components.v1.Etag - 36, // 21: dapr.proto.components.v1.DeleteRequest.metadata:type_name -> dapr.proto.components.v1.DeleteRequest.MetadataEntry + 38, // 21: dapr.proto.components.v1.DeleteRequest.metadata:type_name -> dapr.proto.components.v1.DeleteRequest.MetadataEntry 13, // 22: dapr.proto.components.v1.DeleteRequest.options:type_name -> dapr.proto.components.v1.StateOptions 12, // 23: dapr.proto.components.v1.SetRequest.etag:type_name -> dapr.proto.components.v1.Etag - 37, // 24: dapr.proto.components.v1.SetRequest.metadata:type_name -> dapr.proto.components.v1.SetRequest.MetadataEntry + 39, // 24: dapr.proto.components.v1.SetRequest.metadata:type_name -> dapr.proto.components.v1.SetRequest.MetadataEntry 13, // 25: dapr.proto.components.v1.SetRequest.options:type_name -> dapr.proto.components.v1.StateOptions 18, // 26: dapr.proto.components.v1.BulkDeleteRequest.items:type_name -> dapr.proto.components.v1.DeleteRequest - 16, // 27: dapr.proto.components.v1.BulkGetRequest.items:type_name -> dapr.proto.components.v1.GetRequest - 24, // 28: dapr.proto.components.v1.BulkGetRequest.options:type_name -> dapr.proto.components.v1.BulkGetRequestOptions - 12, // 29: dapr.proto.components.v1.BulkStateItem.etag:type_name -> dapr.proto.components.v1.Etag - 38, // 30: dapr.proto.components.v1.BulkStateItem.metadata:type_name -> dapr.proto.components.v1.BulkStateItem.MetadataEntry - 26, // 31: dapr.proto.components.v1.BulkGetResponse.items:type_name -> dapr.proto.components.v1.BulkStateItem - 20, // 32: dapr.proto.components.v1.BulkSetRequest.items:type_name -> dapr.proto.components.v1.SetRequest - 40, // 33: dapr.proto.components.v1.Query.FilterEntry.value:type_name -> google.protobuf.Any - 6, // 34: dapr.proto.components.v1.QueriableStateStore.Query:input_type -> dapr.proto.components.v1.QueryRequest - 10, // 35: dapr.proto.components.v1.TransactionalStateStore.Transact:input_type -> dapr.proto.components.v1.TransactionalStateRequest - 14, // 36: dapr.proto.components.v1.StateStore.Init:input_type -> dapr.proto.components.v1.InitRequest - 41, // 37: dapr.proto.components.v1.StateStore.Features:input_type -> dapr.proto.components.v1.FeaturesRequest - 18, // 38: dapr.proto.components.v1.StateStore.Delete:input_type -> dapr.proto.components.v1.DeleteRequest - 16, // 39: dapr.proto.components.v1.StateStore.Get:input_type -> dapr.proto.components.v1.GetRequest - 20, // 40: dapr.proto.components.v1.StateStore.Set:input_type -> dapr.proto.components.v1.SetRequest - 42, // 41: dapr.proto.components.v1.StateStore.Ping:input_type -> dapr.proto.components.v1.PingRequest - 22, // 42: dapr.proto.components.v1.StateStore.BulkDelete:input_type -> dapr.proto.components.v1.BulkDeleteRequest - 25, // 43: dapr.proto.components.v1.StateStore.BulkGet:input_type -> dapr.proto.components.v1.BulkGetRequest - 28, // 44: dapr.proto.components.v1.StateStore.BulkSet:input_type -> dapr.proto.components.v1.BulkSetRequest - 8, // 45: dapr.proto.components.v1.QueriableStateStore.Query:output_type -> dapr.proto.components.v1.QueryResponse - 11, // 46: dapr.proto.components.v1.TransactionalStateStore.Transact:output_type -> dapr.proto.components.v1.TransactionalStateResponse - 15, // 47: dapr.proto.components.v1.StateStore.Init:output_type -> dapr.proto.components.v1.InitResponse - 43, // 48: dapr.proto.components.v1.StateStore.Features:output_type -> dapr.proto.components.v1.FeaturesResponse - 19, // 49: dapr.proto.components.v1.StateStore.Delete:output_type -> dapr.proto.components.v1.DeleteResponse - 17, // 50: dapr.proto.components.v1.StateStore.Get:output_type -> dapr.proto.components.v1.GetResponse - 21, // 51: dapr.proto.components.v1.StateStore.Set:output_type -> dapr.proto.components.v1.SetResponse - 44, // 52: dapr.proto.components.v1.StateStore.Ping:output_type -> dapr.proto.components.v1.PingResponse - 23, // 53: dapr.proto.components.v1.StateStore.BulkDelete:output_type -> dapr.proto.components.v1.BulkDeleteResponse - 27, // 54: dapr.proto.components.v1.StateStore.BulkGet:output_type -> dapr.proto.components.v1.BulkGetResponse - 29, // 55: dapr.proto.components.v1.StateStore.BulkSet:output_type -> dapr.proto.components.v1.BulkSetResponse - 45, // [45:56] is the sub-list for method output_type - 34, // [34:45] is the sub-list for method input_type - 34, // [34:34] is the sub-list for extension type_name - 34, // [34:34] is the sub-list for extension extendee - 0, // [0:34] is the sub-list for field type_name + 22, // 27: dapr.proto.components.v1.BulkDeleteRequest.options:type_name -> dapr.proto.components.v1.BulkDeleteRequestOptions + 16, // 28: dapr.proto.components.v1.BulkGetRequest.items:type_name -> dapr.proto.components.v1.GetRequest + 25, // 29: dapr.proto.components.v1.BulkGetRequest.options:type_name -> dapr.proto.components.v1.BulkGetRequestOptions + 12, // 30: dapr.proto.components.v1.BulkStateItem.etag:type_name -> dapr.proto.components.v1.Etag + 40, // 31: dapr.proto.components.v1.BulkStateItem.metadata:type_name -> dapr.proto.components.v1.BulkStateItem.MetadataEntry + 27, // 32: dapr.proto.components.v1.BulkGetResponse.items:type_name -> dapr.proto.components.v1.BulkStateItem + 20, // 33: dapr.proto.components.v1.BulkSetRequest.items:type_name -> dapr.proto.components.v1.SetRequest + 29, // 34: dapr.proto.components.v1.BulkSetRequest.options:type_name -> dapr.proto.components.v1.BulkSetRequestOptions + 42, // 35: dapr.proto.components.v1.Query.FilterEntry.value:type_name -> google.protobuf.Any + 6, // 36: dapr.proto.components.v1.QueriableStateStore.Query:input_type -> dapr.proto.components.v1.QueryRequest + 10, // 37: dapr.proto.components.v1.TransactionalStateStore.Transact:input_type -> dapr.proto.components.v1.TransactionalStateRequest + 14, // 38: dapr.proto.components.v1.StateStore.Init:input_type -> dapr.proto.components.v1.InitRequest + 43, // 39: dapr.proto.components.v1.StateStore.Features:input_type -> dapr.proto.components.v1.FeaturesRequest + 18, // 40: dapr.proto.components.v1.StateStore.Delete:input_type -> dapr.proto.components.v1.DeleteRequest + 16, // 41: dapr.proto.components.v1.StateStore.Get:input_type -> dapr.proto.components.v1.GetRequest + 20, // 42: dapr.proto.components.v1.StateStore.Set:input_type -> dapr.proto.components.v1.SetRequest + 44, // 43: dapr.proto.components.v1.StateStore.Ping:input_type -> dapr.proto.components.v1.PingRequest + 23, // 44: dapr.proto.components.v1.StateStore.BulkDelete:input_type -> dapr.proto.components.v1.BulkDeleteRequest + 26, // 45: dapr.proto.components.v1.StateStore.BulkGet:input_type -> dapr.proto.components.v1.BulkGetRequest + 30, // 46: dapr.proto.components.v1.StateStore.BulkSet:input_type -> dapr.proto.components.v1.BulkSetRequest + 8, // 47: dapr.proto.components.v1.QueriableStateStore.Query:output_type -> dapr.proto.components.v1.QueryResponse + 11, // 48: dapr.proto.components.v1.TransactionalStateStore.Transact:output_type -> dapr.proto.components.v1.TransactionalStateResponse + 15, // 49: dapr.proto.components.v1.StateStore.Init:output_type -> dapr.proto.components.v1.InitResponse + 45, // 50: dapr.proto.components.v1.StateStore.Features:output_type -> dapr.proto.components.v1.FeaturesResponse + 19, // 51: dapr.proto.components.v1.StateStore.Delete:output_type -> dapr.proto.components.v1.DeleteResponse + 17, // 52: dapr.proto.components.v1.StateStore.Get:output_type -> dapr.proto.components.v1.GetResponse + 21, // 53: dapr.proto.components.v1.StateStore.Set:output_type -> dapr.proto.components.v1.SetResponse + 46, // 54: dapr.proto.components.v1.StateStore.Ping:output_type -> dapr.proto.components.v1.PingResponse + 24, // 55: dapr.proto.components.v1.StateStore.BulkDelete:output_type -> dapr.proto.components.v1.BulkDeleteResponse + 28, // 56: dapr.proto.components.v1.StateStore.BulkGet:output_type -> dapr.proto.components.v1.BulkGetResponse + 31, // 57: dapr.proto.components.v1.StateStore.BulkSet:output_type -> dapr.proto.components.v1.BulkSetResponse + 47, // [47:58] is the sub-list for method output_type + 36, // [36:47] is the sub-list for method input_type + 36, // [36:36] is the sub-list for extension type_name + 36, // [36:36] is the sub-list for extension extendee + 0, // [0:36] is the sub-list for field type_name } func init() { file_dapr_proto_components_v1_state_proto_init() } @@ -2453,7 +2584,7 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BulkDeleteRequest); i { + switch v := v.(*BulkDeleteRequestOptions); i { case 0: return &v.state case 1: @@ -2465,7 +2596,7 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BulkDeleteResponse); i { + switch v := v.(*BulkDeleteRequest); i { case 0: return &v.state case 1: @@ -2477,7 +2608,7 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BulkGetRequestOptions); i { + switch v := v.(*BulkDeleteResponse); i { case 0: return &v.state case 1: @@ -2489,7 +2620,7 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BulkGetRequest); i { + switch v := v.(*BulkGetRequestOptions); i { case 0: return &v.state case 1: @@ -2501,7 +2632,7 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BulkStateItem); i { + switch v := v.(*BulkGetRequest); i { case 0: return &v.state case 1: @@ -2513,7 +2644,7 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BulkGetResponse); i { + switch v := v.(*BulkStateItem); i { case 0: return &v.state case 1: @@ -2525,7 +2656,7 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BulkSetRequest); i { + switch v := v.(*BulkGetResponse); i { case 0: return &v.state case 1: @@ -2537,6 +2668,30 @@ func file_dapr_proto_components_v1_state_proto_init() { } } file_dapr_proto_components_v1_state_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BulkSetRequestOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dapr_proto_components_v1_state_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BulkSetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dapr_proto_components_v1_state_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BulkSetResponse); i { case 0: return &v.state @@ -2559,7 +2714,7 @@ func file_dapr_proto_components_v1_state_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_dapr_proto_components_v1_state_proto_rawDesc, NumEnums: 3, - NumMessages: 36, + NumMessages: 38, NumExtensions: 0, NumServices: 3, }, diff --git a/pkg/runtime/pubsub/bulkpublish_resiliency.go b/pkg/runtime/pubsub/bulkpublish_resiliency.go index e21c528023b..7ff4732a639 100644 --- a/pkg/runtime/pubsub/bulkpublish_resiliency.go +++ b/pkg/runtime/pubsub/bulkpublish_resiliency.go @@ -32,15 +32,17 @@ func ApplyBulkPublishResiliency(ctx context.Context, req *contribPubsub.BulkPubl policyRunner := resiliency.NewRunnerWithOptions(ctx, policyDef, resiliency.RunnerOpts[contribPubsub.BulkPublishResponse]{ Accumulator: func(res contribPubsub.BulkPublishResponse) { - if len(res.FailedEntries) > 0 { - // requestEntries can be modified here as Accumulator is executed synchronously - failedEntryIds := extractEntryIds(res.FailedEntries) - filteredEntries := utils.Filter(*requestEntries.Load(), func(item contribPubsub.BulkMessageEntry) bool { - _, ok := failedEntryIds[item.EntryId] - return ok - }) - requestEntries.Store(&filteredEntries) + if len(res.FailedEntries) == 0 { + return } + + // requestEntries can be modified here as Accumulator is executed synchronously + failedEntryIds := extractEntryIds(res.FailedEntries) + filteredEntries := utils.Filter(*requestEntries.Load(), func(item contribPubsub.BulkMessageEntry) bool { + _, ok := failedEntryIds[item.EntryId] + return ok + }) + requestEntries.Store(&filteredEntries) }, }) res, err := policyRunner(func(ctx context.Context) (contribPubsub.BulkPublishResponse, error) { diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 09fb7a87b65..fd1afb61c33 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -1291,19 +1291,19 @@ func (a *DaprRuntime) sendToOutputBinding(name string, req *bindings.InvokeReque func (a *DaprRuntime) onAppResponse(response *bindings.AppResponse) error { if len(response.State) > 0 { go func(reqs []state.SetRequest) { - state, ok := a.compStore.GetStateStore(response.StoreName) + store, ok := a.compStore.GetStateStore(response.StoreName) if !ok { return } - policyRunner := resiliency.NewRunner[any](a.ctx, + err := stateLoader.PerformBulkStoreOperation(a.ctx, reqs, a.resiliency.ComponentOutboundPolicy(response.StoreName, resiliency.Statestore), + state.BulkStoreOpts{}, + store.Set, + store.BulkSet, ) - _, err := policyRunner(func(ctx context.Context) (any, error) { - return nil, state.BulkSet(ctx, reqs) - }) if err != nil { - log.Errorf("error saving state from app response: %s", err) + log.Errorf("error saving state from app response: %v", err) } }(response.State) } @@ -1326,7 +1326,7 @@ func (a *DaprRuntime) onAppResponse(response *bindings.AppResponse) error { func (a *DaprRuntime) sendBindingEventToApp(bindingName string, data []byte, metadata map[string]string) ([]byte, error) { var response bindings.AppResponse - spanName := fmt.Sprintf("bindings/%s", bindingName) + spanName := "bindings/" + bindingName spanContext := trace.SpanContext{} // Check the grpc-trace-bin with fallback to traceparent. diff --git a/pkg/testing/fake_state_store.go b/pkg/testing/fake_state_store.go index 04810a3bfc6..8d31556cb79 100644 --- a/pkg/testing/fake_state_store.go +++ b/pkg/testing/fake_state_store.go @@ -74,7 +74,7 @@ func (f *FakeStateStore) Delete(ctx context.Context, req *state.DeleteRequest) e return nil } -func (f *FakeStateStore) BulkDelete(ctx context.Context, req []state.DeleteRequest) error { +func (f *FakeStateStore) BulkDelete(ctx context.Context, req []state.DeleteRequest, opts state.BulkStoreOpts) error { return nil } @@ -130,7 +130,7 @@ func (f *FakeStateStore) GetComponentMetadata() map[string]string { return map[string]string{} } -func (f *FakeStateStore) BulkSet(ctx context.Context, req []state.SetRequest) error { +func (f *FakeStateStore) BulkSet(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { return nil } diff --git a/pkg/testing/state_mock.go b/pkg/testing/state_mock.go index 2a64738f04e..055654db2c7 100644 --- a/pkg/testing/state_mock.go +++ b/pkg/testing/state_mock.go @@ -26,8 +26,7 @@ type MockStateStore struct { mock.Mock } -// BulkDelete provides a mock function with given fields: req -func (_m *MockStateStore) BulkDelete(ctx context.Context, req []state.DeleteRequest) error { +func (_m *MockStateStore) BulkDelete(ctx context.Context, req []state.DeleteRequest, opts state.BulkStoreOpts) error { ret := _m.Called(ctx, req) var r0 error @@ -40,8 +39,7 @@ func (_m *MockStateStore) BulkDelete(ctx context.Context, req []state.DeleteRequ return r0 } -// BulkSet provides a mock function with given fields: req -func (_m *MockStateStore) BulkSet(ctx context.Context, req []state.SetRequest) error { +func (_m *MockStateStore) BulkSet(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { ret := _m.Called(ctx, req) var r0 error @@ -148,7 +146,7 @@ func (f *FailingStatestore) GetComponentMetadata() map[string]string { return map[string]string{} } -func (f *FailingStatestore) BulkDelete(ctx context.Context, req []state.DeleteRequest) error { +func (f *FailingStatestore) BulkDelete(ctx context.Context, req []state.DeleteRequest, opts state.BulkStoreOpts) error { for _, val := range req { err := f.Failure.PerformFailure(val.Key) if err != nil { @@ -158,7 +156,7 @@ func (f *FailingStatestore) BulkDelete(ctx context.Context, req []state.DeleteRe return nil } -func (f *FailingStatestore) BulkSet(ctx context.Context, req []state.SetRequest) error { +func (f *FailingStatestore) BulkSet(ctx context.Context, req []state.SetRequest, opts state.BulkStoreOpts) error { for _, val := range req { err := f.Failure.PerformFailure(val.Key) if err != nil { diff --git a/tests/apps/perf/configuration/app.go b/tests/apps/perf/configuration/app.go index 24aa02fd624..622aa9550c1 100644 --- a/tests/apps/perf/configuration/app.go +++ b/tests/apps/perf/configuration/app.go @@ -58,10 +58,6 @@ type appResponse struct { EndTime int `json:"end_time,omitempty"` } -type receivedMessagesResponse struct { - ReceivedUpdates []string `json:"received-messages"` -} - type Item struct { Value string `json:"value,omitempty"` Version string `json:"version,omitempty"` @@ -498,7 +494,7 @@ func updateKeyValues(w http.ResponseWriter, r *http.Request) { } // appRouter initializes restful api router -func appRouter() *mux.Router { +func appRouter() http.Handler { router := mux.NewRouter().StrictSlash(true) // Log requests and their processing time diff --git a/tests/apps/perf/configuration/go.mod b/tests/apps/perf/configuration/go.mod deleted file mode 100644 index 8e079d50a68..00000000000 --- a/tests/apps/perf/configuration/go.mod +++ /dev/null @@ -1,22 +0,0 @@ -module github.com/dapr/dapr/tests/apps/perf/configuration - -go 1.20 - -require ( - github.com/dapr/dapr v1.10.5-rc.1.0.20230419110032-3fb850d12c53 - github.com/google/uuid v1.3.0 - github.com/gorilla/mux v1.8.0 - github.com/redis/go-redis/v9 v9.0.3 -) - -require ( - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/golang/protobuf v1.5.3 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect -) diff --git a/tests/apps/perf/configuration/go.sum b/tests/apps/perf/configuration/go.sum deleted file mode 100644 index 7d50f328952..00000000000 --- a/tests/apps/perf/configuration/go.sum +++ /dev/null @@ -1,34 +0,0 @@ -github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/dapr/dapr v1.10.5-rc.1.0.20230419110032-3fb850d12c53 h1:8NtcXbBr//IWknO/X5clpWx7QJ77w5ZHq8lq+7E2MzY= -github.com/dapr/dapr v1.10.5-rc.1.0.20230419110032-3fb850d12c53/go.mod h1:45XQnrUKbBq/yRbmc/eS/hCKjo4gx7SQYsHUoB9bydg= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= -github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/tests/apps/resiliencyapp/go.mod b/tests/apps/resiliencyapp/go.mod index 296cd553adc..7dd1497e164 100644 --- a/tests/apps/resiliencyapp/go.mod +++ b/tests/apps/resiliencyapp/go.mod @@ -13,8 +13,8 @@ require ( require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect ) diff --git a/tests/apps/resiliencyapp/go.sum b/tests/apps/resiliencyapp/go.sum index 9fce6a70905..16b3c27a8ce 100644 --- a/tests/apps/resiliencyapp/go.sum +++ b/tests/apps/resiliencyapp/go.sum @@ -50,8 +50,8 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -60,8 +60,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/tests/apps/resiliencyapp_grpc/go.mod b/tests/apps/resiliencyapp_grpc/go.mod index 321ef59cc01..39d887638e4 100644 --- a/tests/apps/resiliencyapp_grpc/go.mod +++ b/tests/apps/resiliencyapp_grpc/go.mod @@ -11,8 +11,8 @@ require ( require ( github.com/golang/protobuf v1.5.3 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect ) diff --git a/tests/apps/resiliencyapp_grpc/go.sum b/tests/apps/resiliencyapp_grpc/go.sum index 01422f5dada..4e0497b5f57 100644 --- a/tests/apps/resiliencyapp_grpc/go.sum +++ b/tests/apps/resiliencyapp_grpc/go.sum @@ -46,8 +46,8 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -56,8 +56,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/tests/apps/secretapp/app.go b/tests/apps/secretapp/app.go index aa104a3cdf1..d082339aa87 100644 --- a/tests/apps/secretapp/app.go +++ b/tests/apps/secretapp/app.go @@ -247,7 +247,7 @@ func createSecretURL(key, store string) (string, error) { // epoch returns the current unix epoch timestamp func epoch() int { - return (int)(time.Now().UTC().UnixNano() / 1000000) + return int(time.Now().UnixMilli()) } // appRouter initializes restful api router diff --git a/tests/apps/service_invocation_grpc_proxy_client/go.mod b/tests/apps/service_invocation_grpc_proxy_client/go.mod index cdd03b07f54..050931ed625 100644 --- a/tests/apps/service_invocation_grpc_proxy_client/go.mod +++ b/tests/apps/service_invocation_grpc_proxy_client/go.mod @@ -12,8 +12,8 @@ require ( require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/tests/apps/service_invocation_grpc_proxy_client/go.sum b/tests/apps/service_invocation_grpc_proxy_client/go.sum index 9ce926fc305..1cb45bec0cd 100644 --- a/tests/apps/service_invocation_grpc_proxy_client/go.sum +++ b/tests/apps/service_invocation_grpc_proxy_client/go.sum @@ -50,8 +50,8 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -60,8 +60,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/tests/apps/stateapp/app.go b/tests/apps/stateapp/app.go index 0142f71ab9c..b23a826c25f 100644 --- a/tests/apps/stateapp/app.go +++ b/tests/apps/stateapp/app.go @@ -26,9 +26,11 @@ import ( "path" "strconv" "strings" - "time" + "github.com/google/uuid" "github.com/gorilla/mux" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" daprhttp "github.com/dapr/dapr/pkg/http" commonv1pb "github.com/dapr/dapr/pkg/proto/common/v1" @@ -45,6 +47,7 @@ const ( metadataPartitionKey = "partitionKey" partitionKey = "e2etest" + badEtag = "99999" // Must be numeric because of Redis ) var ( @@ -80,6 +83,7 @@ type appState struct { type daprState struct { Key string `json:"key,omitempty"` Value *appState `json:"value,omitempty"` + Etag string `json:"etag,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` OperationType string `json:"operationType,omitempty"` } @@ -93,17 +97,15 @@ type bulkGetRequest struct { // bulkGetResponse is the response object from Dapr for a bulk get operation. type bulkGetResponse struct { - Key string `json:"key"` - Data interface{} `json:"data"` - ETag string `json:"etag"` + Key string `json:"key"` + Data any `json:"data"` + ETag string `json:"etag"` } // requestResponse represents a request or response for the APIs in this app. type requestResponse struct { - StartTime int `json:"start_time,omitempty"` - EndTime int `json:"end_time,omitempty"` - States []daprState `json:"states,omitempty"` - Message string `json:"message,omitempty"` + States []daprState `json:"states,omitempty"` + Message string `json:"message,omitempty"` } // indexHandler is the handler for root path @@ -131,7 +133,7 @@ func load(data []byte, statestore string, meta map[string]string) (int, error) { stateURL += "?" + metadata2RawQuery(meta) } log.Printf("Posting %d bytes of state to %s", len(data), stateURL) - res, err := httpClient.Post(stateURL, "application/json", bytes.NewBuffer(data)) + res, err := httpClient.Post(stateURL, "application/json", bytes.NewReader(data)) if err != nil { return http.StatusInternalServerError, err } @@ -139,16 +141,21 @@ func load(data []byte, statestore string, meta map[string]string) (int, error) { // Save must return 204 if res.StatusCode != http.StatusNoContent { - err = fmt.Errorf("expected status code 204, got %d", res.StatusCode) + body, _ := io.ReadAll(res.Body) + err = fmt.Errorf("expected status code 204, got %d; response: %s", res.StatusCode, string(body)) } + + // Drain before closing + _, _ = io.Copy(io.Discard, res.Body) + return res.StatusCode, err } -func get(key, statestore string, meta map[string]string) (*appState, error) { +func get(key string, statestore string, meta map[string]string) (*appState, string, error) { log.Printf("Processing get request for %s.", key) url, err := createStateURL(key, statestore, meta) if err != nil { - return nil, err + return nil, "", err } log.Printf("Fetching state from %s", url) @@ -156,27 +163,27 @@ func get(key, statestore string, meta map[string]string) (*appState, error) { /* #nosec */ res, err := httpClient.Get(url) if err != nil { - return nil, fmt.Errorf("could not get value for key %s from Dapr: %s", key, err.Error()) + return nil, "", fmt.Errorf("could not get value for key %s from Dapr: %s", key, err.Error()) } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { - return nil, fmt.Errorf("could not load value for key %s from Dapr: %s", key, err.Error()) + return nil, "", fmt.Errorf("could not load value for key %s from Dapr: %s", key, err.Error()) } if res.StatusCode < 200 || res.StatusCode > 299 { - return nil, fmt.Errorf("failed to get value for key %s from Dapr: %s", key, body) + return nil, "", fmt.Errorf("failed to get value for key %s from Dapr: %s", key, body) } log.Printf("Found state for key %s: %s", key, body) state, err := parseState(key, body) if err != nil { - return nil, err + return nil, "", err } - return state, nil + return state, res.Header.Get("etag"), nil } func parseState(key string, body []byte) (*appState, error) { @@ -203,7 +210,7 @@ func getAll(states []daprState, statestore string, meta map[string]string) ([]da output := make([]daprState, 0, len(states)) for _, state := range states { - value, err := get(state.Key, statestore, meta) + value, etag, err := get(state.Key, statestore, meta) if err != nil { return nil, err } @@ -212,6 +219,7 @@ func getAll(states []daprState, statestore string, meta map[string]string) ([]da output = append(output, daprState{ Key: state.Key, Value: value, + Etag: etag, }) } @@ -240,7 +248,7 @@ func getBulk(states []daprState, statestore string) ([]daprState, error) { return nil, err } - res, err := httpClient.Post(url, "application/json", bytes.NewBuffer(b)) + res, err := httpClient.Post(url, "application/json", bytes.NewReader(b)) if err != nil { return nil, err } @@ -260,17 +268,18 @@ func getBulk(states []daprState, statestore string) ([]daprState, error) { return nil, fmt.Errorf("could not unmarshal bulk get response from Dapr: %s", err.Error()) } - for _, i := range resp { + for _, state := range resp { var as appState - b, err := json.Marshal(i.Data) + b, err := json.Marshal(state.Data) if err != nil { return nil, fmt.Errorf("could not marshal return data: %s", err) } json.Unmarshal(b, &as) output = append(output, daprState{ - Key: i.Key, + Key: state.Key, Value: &as, + Etag: state.ETag, }) } @@ -278,37 +287,44 @@ func getBulk(states []daprState, statestore string) ([]daprState, error) { return output, nil } -func delete(key, statestore string, meta map[string]string) error { - log.Printf("Processing delete request for %s.", key) +func delete(key, statestore string, meta map[string]string, etag string) (int, error) { + log.Printf("Processing delete request for %s", key) url, err := createStateURL(key, statestore, meta) if err != nil { - return err + return 0, err } req, err := http.NewRequest(http.MethodDelete, url, nil) if err != nil { - return fmt.Errorf("could not create delete request for key %s in Dapr: %s", key, err.Error()) + return 0, fmt.Errorf("could not create delete request for key %s in Dapr: %s", key, err.Error()) + } + if etag != "" { + req.Header.Set("If-Match", etag) } log.Printf("Deleting state for %s", url) res, err := httpClient.Do(req) if err != nil { - return fmt.Errorf("could not delete key %s in Dapr: %s", key, err.Error()) + return 0, fmt.Errorf("could not delete key %s in Dapr: %s", key, err.Error()) } defer res.Body.Close() if res.StatusCode < 200 || res.StatusCode > 299 { - return fmt.Errorf("failed to delete key %s in Dapr: %s", key, err.Error()) + body, _ := io.ReadAll(res.Body) + return res.StatusCode, fmt.Errorf("failed to delete key %s in Dapr: %s", key, string(body)) } - return nil + // Drain before closing + _, _ = io.Copy(io.Discard, res.Body) + + return res.StatusCode, nil } func deleteAll(states []daprState, statestore string, meta map[string]string) error { log.Printf("Processing delete request for %d states.", len(states)) for _, state := range states { - err := delete(state.Key, statestore, meta) + _, err := delete(state.Key, statestore, meta, "") if err != nil { return err } @@ -340,7 +356,7 @@ func executeTransaction(states []daprState, statestore string) error { } log.Printf("Posting state to %s with '%s'", stateTransactionURL, jsonValue) - res, err := httpClient.Post(stateTransactionURL, "application/json", bytes.NewBuffer(jsonValue)) + res, err := httpClient.Post(stateTransactionURL, "application/json", bytes.NewReader(jsonValue)) if err != nil { return err } @@ -358,7 +374,7 @@ func executeQuery(query []byte, statestore string, meta map[string]string) ([]da queryURL += "?" + metadata2RawQuery(meta) } log.Printf("Posting %d bytes of state to %s", len(query), queryURL) - resp, err := httpClient.Post(queryURL, "application/json", bytes.NewBuffer(query)) + resp, err := httpClient.Post(queryURL, "application/json", bytes.NewReader(query)) if err != nil { return nil, err } @@ -395,7 +411,8 @@ func executeQuery(query []byte, statestore string, meta map[string]string) ([]da func parseRequestBody(w http.ResponseWriter, r *http.Request) (*requestResponse, error) { req := &requestResponse{} - if err := json.NewDecoder(r.Body).Decode(req); err != nil { + err := json.NewDecoder(r.Body).Decode(req) + if err != nil { log.Printf("Could not parse request body: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(requestResponse{ @@ -411,7 +428,8 @@ func parseRequestBody(w http.ResponseWriter, r *http.Request) (*requestResponse, } func getRequestBody(w http.ResponseWriter, r *http.Request) (data []byte, err error) { - if data, err = io.ReadAll(r.Body); err != nil { + data, err = io.ReadAll(r.Body) + if err != nil { log.Printf("Could not read request body: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(requestResponse{ @@ -453,14 +471,13 @@ func httpHandler(w http.ResponseWriter, r *http.Request) { uri := r.URL.RequestURI() statusCode := http.StatusOK - res.StartTime = epoch() - cmd := mux.Vars(r)["command"] statestore := mux.Vars(r)["statestore"] meta := getMetadata(r.URL.Query()) switch cmd { case "save": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } _, err = save(req.States, statestore, meta) @@ -470,33 +487,39 @@ func httpHandler(w http.ResponseWriter, r *http.Request) { statusCode = http.StatusNoContent } case "load": - if data, err = getRequestBody(w, r); err != nil { + data, err = getRequestBody(w, r) + if err != nil { return } statusCode, err = load(data, statestore, meta) case "get": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } res.States, err = getAll(req.States, statestore, meta) case "getbulk": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } res.States, err = getBulk(req.States, statestore) case "delete": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } err = deleteAll(req.States, statestore, meta) statusCode = http.StatusNoContent case "transact": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } err = executeTransaction(req.States, statestore) case "query": - if data, err = getRequestBody(w, r); err != nil { + data, err = getRequestBody(w, r) + if err != nil { return } res.States, err = executeQuery(data, statestore, meta) @@ -512,8 +535,6 @@ func httpHandler(w http.ResponseWriter, r *http.Request) { res.Message = err.Error() } - res.EndTime = epoch() - if !statusCheck { log.Printf("Error status code %v: %v", statusCode, res.Message) } @@ -532,7 +553,6 @@ func grpcHandler(w http.ResponseWriter, r *http.Request) { var err error var res requestResponse var response *runtimev1pb.GetBulkStateResponse - res.StartTime = epoch() statusCode := http.StatusOK cmd := mux.Vars(r)["command"] @@ -571,7 +591,8 @@ func grpcHandler(w http.ResponseWriter, r *http.Request) { } res.States = states case "get": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } states, err = getAllGRPC(req.States, statestore, meta) @@ -580,7 +601,8 @@ func grpcHandler(w http.ResponseWriter, r *http.Request) { } res.States = states case "delete": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } statusCode = http.StatusNoContent @@ -589,7 +611,8 @@ func grpcHandler(w http.ResponseWriter, r *http.Request) { statusCode, res.Message = setErrorMessage("DeleteState", err.Error()) } case "transact": - if req, err = parseRequestBody(w, r); err != nil { + req, err = parseRequestBody(w, r) + if err != nil { return } _, err = grpcClient.ExecuteStateTransaction(context.Background(), &runtimev1pb.ExecuteStateTransactionRequest{ @@ -601,7 +624,8 @@ func grpcHandler(w http.ResponseWriter, r *http.Request) { statusCode, res.Message = setErrorMessage("ExecuteStateTransaction", err.Error()) } case "query": - if data, err = getRequestBody(w, r); err != nil { + data, err = getRequestBody(w, r) + if err != nil { return } resp, err := grpcClient.QueryStateAlpha1(context.Background(), &runtimev1pb.QueryStateRequest{ @@ -628,7 +652,6 @@ func grpcHandler(w http.ResponseWriter, r *http.Request) { res.Message = unsupportedCommandMessage } - res.EndTime = epoch() if statusCode != http.StatusOK && statusCode != http.StatusNoContent { log.Printf("Error status code %v: %v", statusCode, res.Message) } @@ -659,30 +682,42 @@ func toDaprStates(response *runtimev1pb.GetBulkStateResponse) ([]daprState, erro result[i] = daprState{ Key: state.Key, Value: daprStateItem, + Etag: state.Etag, } } return result, nil } -func deleteAllGRPC(states []daprState, statestore string, meta map[string]string) error { - m := map[string]string{metadataPartitionKey: partitionKey} - for k, v := range meta { - m[k] = v +func deleteAllGRPC(states []daprState, statestore string, meta map[string]string) (err error) { + if len(states) == 0 { + return nil } - for _, state := range states { - log.Printf("deleting sate for key %s\n", state.Key) - _, err := grpcClient.DeleteState(context.Background(), &runtimev1pb.DeleteStateRequest{ + + if len(states) == 1 { + log.Print("deleting sate for key", states[0].Key) + m := map[string]string{metadataPartitionKey: partitionKey} + for k, v := range meta { + m[k] = v + } + _, err = grpcClient.DeleteState(context.Background(), &runtimev1pb.DeleteStateRequest{ StoreName: statestore, - Key: state.Key, + Key: states[0].Key, Metadata: m, }) - if err != nil { - return err - } + return err } - return nil + keys := make([]string, len(states)) + for i, state := range states { + keys[i] = state.Key + } + log.Print("deleting bulk sates for keys", keys) + _, err = grpcClient.DeleteBulkState(context.Background(), &runtimev1pb.DeleteBulkStateRequest{ + StoreName: statestore, + States: daprState2StateItems(states, meta), + }) + return err } func getAllGRPC(states []daprState, statestore string, meta map[string]string) ([]daprState, error) { @@ -716,8 +751,7 @@ func getAllGRPC(states []daprState, statestore string, meta map[string]string) ( } func setErrorMessage(method, errorString string) (int, string) { - log.Printf("GRPC %s had error %s\n", method, errorString) - + log.Printf("GRPC %s had error %s", method, errorString) return http.StatusInternalServerError, errorString } @@ -734,6 +768,11 @@ func daprState2StateItems(daprStates []daprState, meta map[string]string) []*com Value: val, Metadata: m, } + if daprState.Etag != "" { + stateItems[i].Etag = &commonv1pb.Etag{ + Value: daprState.Etag, + } + } } return stateItems @@ -783,9 +822,400 @@ func createBulkStateURL(statestore string) (string, error) { return url.String(), nil } -// epoch returns the current unix epoch timestamp -func epoch() int { - return (int)(time.Now().UTC().UnixNano() / 1000000) +// Etag test for HTTP +func etagTestHTTP(statestore string) error { + pkMetadata := map[string]string{metadataPartitionKey: partitionKey} + + // Use two random keys for testing + var etags [2]string + keys := [2]string{ + uuid.NewString(), + uuid.NewString(), + } + + type retrieveStateOpts struct { + expectNotFound bool + expectValue string + expectEtagEqual string + expectEtagNotEqual string + } + retrieveState := func(stateId int, opts retrieveStateOpts) (string, error) { + value, etag, err := get(keys[stateId], statestore, pkMetadata) + if err != nil { + return "", fmt.Errorf("failed to retrieve value %d: %w", stateId, err) + } + + if opts.expectNotFound { + if value != nil && len(value.Data) != 0 { + return "", fmt.Errorf("invalid value for state %d: %#v (expected empty)", stateId, value) + } + return "", nil + } + if value == nil || string(value.Data) != opts.expectValue { + return "", fmt.Errorf("invalid value for state %d: %#v (expected: %q)", stateId, value, opts.expectValue) + } + if etag == "" { + return "", fmt.Errorf("etag is empty for state %d", stateId) + } + if opts.expectEtagEqual != "" && etag != opts.expectEtagEqual { + return "", fmt.Errorf("etag is invalid for state %d: %q (expected: %q)", stateId, etag, opts.expectEtagEqual) + } + if opts.expectEtagNotEqual != "" && etag == opts.expectEtagNotEqual { + return "", fmt.Errorf("etag is invalid for state %d: %q (expected different value)", stateId, etag) + } + return etag, nil + } + + // First, write two values + _, err := save([]daprState{ + {Key: keys[0], Value: &appState{Data: []byte("1")}, Metadata: pkMetadata}, + {Key: keys[1], Value: &appState{Data: []byte("1")}, Metadata: pkMetadata}, + }, statestore, pkMetadata) + if err != nil { + return fmt.Errorf("failed to store initial values: %w", err) + } + + // Retrieve the two values to get the etag + etags[0], err = retrieveState(0, retrieveStateOpts{expectValue: "1"}) + if err != nil { + return fmt.Errorf("failed to check initial value for state 0: %w", err) + } + etags[1], err = retrieveState(1, retrieveStateOpts{expectValue: "1"}) + if err != nil { + return fmt.Errorf("failed to check initial value for state 1: %w", err) + } + + // Update the first state using the correct etag + _, err = save([]daprState{ + {Key: keys[0], Value: &appState{Data: []byte("2")}, Metadata: pkMetadata, Etag: etags[0]}, + }, statestore, pkMetadata) + if err != nil { + return fmt.Errorf("failed to update value 0: %w", err) + } + + // Check the first state + etags[0], err = retrieveState(0, retrieveStateOpts{expectValue: "2", expectEtagNotEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check initial value for state 0: %w", err) + } + + // Updating with wrong etag should fail with 409 status code + statusCode, _ := save([]daprState{ + {Key: keys[1], Value: &appState{Data: []byte("2")}, Metadata: pkMetadata, Etag: badEtag}, + }, statestore, pkMetadata) + if statusCode != http.StatusConflict { + return fmt.Errorf("expected update with invalid etag to fail with status code 409, but got: %d", statusCode) + } + + // Value should not have changed + _, err = retrieveState(1, retrieveStateOpts{expectValue: "1", expectEtagEqual: etags[1]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 1: %w", err) + } + + // Bulk update with all valid etags + _, err = save([]daprState{ + {Key: keys[0], Value: &appState{Data: []byte("3")}, Metadata: pkMetadata, Etag: etags[0]}, + {Key: keys[1], Value: &appState{Data: []byte("3")}, Metadata: pkMetadata, Etag: etags[1]}, + }, statestore, pkMetadata) + if err != nil { + return fmt.Errorf("failed to update bulk values: %w", err) + } + + // Retrieve the two values to confirm they're updated + etags[0], err = retrieveState(0, retrieveStateOpts{expectValue: "3", expectEtagNotEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 0: %w", err) + } + etags[1], err = retrieveState(1, retrieveStateOpts{expectValue: "3", expectEtagNotEqual: etags[1]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 1: %w", err) + } + + // Bulk update with one etag incorrect + statusCode, _ = save([]daprState{ + {Key: keys[0], Value: &appState{Data: []byte("4")}, Metadata: pkMetadata, Etag: badEtag}, + {Key: keys[1], Value: &appState{Data: []byte("4")}, Metadata: pkMetadata, Etag: etags[1]}, + }, statestore, pkMetadata) + if statusCode != http.StatusConflict { + return fmt.Errorf("expected update with invalid etag to fail with status code 409, but got: %d", statusCode) + } + + // Retrieve the two values to confirm only the second is updated + _, err = retrieveState(0, retrieveStateOpts{expectValue: "3", expectEtagEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 0: %w", err) + } + etags[1], err = retrieveState(1, retrieveStateOpts{expectValue: "4", expectEtagNotEqual: etags[1]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 1: %w", err) + } + + // Delete single item with incorrect etag + statusCode, _ = delete(keys[0], statestore, pkMetadata, badEtag) + if statusCode != http.StatusConflict { + return fmt.Errorf("expected delete with invalid etag to fail with status code 409, but got: %d", statusCode) + } + + // Value should not have changed + _, err = retrieveState(0, retrieveStateOpts{expectValue: "3", expectEtagEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 0: %w", err) + } + + // TODO: There's no "Bulk Delete" API in HTTP right now, so we can't test that + // Create a test here when the API is implemented + err = deleteAll([]daprState{ + {Key: keys[0], Metadata: pkMetadata}, + {Key: keys[1], Metadata: pkMetadata}, + }, statestore, pkMetadata) + if err != nil { + return fmt.Errorf("failed to delete all data at the end of the test: %w", err) + } + + return nil +} + +// Etag test for gRPC +func etagTestGRPC(statestore string) error { + pkMetadata := map[string]string{metadataPartitionKey: partitionKey} + + // Use three random keys for testing + var etags [3]string + keys := [3]string{ + uuid.NewString(), + uuid.NewString(), + uuid.NewString(), + } + + type retrieveStateOpts struct { + expectNotFound bool + expectValue string + expectEtagEqual string + expectEtagNotEqual string + } + retrieveState := func(stateId int, opts retrieveStateOpts) (string, error) { + res, err := grpcClient.GetState(context.Background(), &runtimev1pb.GetStateRequest{ + StoreName: statestore, + Key: keys[stateId], + Metadata: pkMetadata, + }) + if err != nil { + return "", fmt.Errorf("failed to retrieve value %d: %w", stateId, err) + } + + if opts.expectNotFound { + if len(res.Data) != 0 { + return "", fmt.Errorf("invalid value for state %d: %q (expected empty)", stateId, string(res.Data)) + } + return "", nil + } + if len(res.Data) == 0 || string(res.Data) != opts.expectValue { + return "", fmt.Errorf("invalid value for state %d: %q (expected: %q)", stateId, string(res.Data), opts.expectValue) + } + if res.Etag == "" { + return "", fmt.Errorf("etag is empty for state %d", stateId) + } + if opts.expectEtagEqual != "" && res.Etag != opts.expectEtagEqual { + return "", fmt.Errorf("etag is invalid for state %d: %q (expected: %q)", stateId, res.Etag, opts.expectEtagEqual) + } + if opts.expectEtagNotEqual != "" && res.Etag == opts.expectEtagNotEqual { + return "", fmt.Errorf("etag is invalid for state %d: %q (expected different value)", stateId, res.Etag) + } + return res.Etag, nil + } + + // First, write three values + _, err := grpcClient.SaveState(context.Background(), &runtimev1pb.SaveStateRequest{ + StoreName: statestore, + States: []*commonv1pb.StateItem{ + {Key: keys[0], Value: []byte("1"), Metadata: pkMetadata}, + {Key: keys[1], Value: []byte("1"), Metadata: pkMetadata}, + {Key: keys[2], Value: []byte("1"), Metadata: pkMetadata}, + }, + }) + if err != nil { + return fmt.Errorf("failed to store initial values: %w", err) + } + + // Retrieve the two values to get the etag + etags[0], err = retrieveState(0, retrieveStateOpts{expectValue: "1"}) + if err != nil { + return fmt.Errorf("failed to check initial value for state 0: %w", err) + } + etags[1], err = retrieveState(1, retrieveStateOpts{expectValue: "1"}) + if err != nil { + return fmt.Errorf("failed to check initial value for state 1: %w", err) + } + etags[2], err = retrieveState(2, retrieveStateOpts{expectValue: "1"}) + if err != nil { + return fmt.Errorf("failed to check initial value for state 2: %w", err) + } + + // Update the first state using the correct etag + _, err = grpcClient.SaveState(context.Background(), &runtimev1pb.SaveStateRequest{ + StoreName: statestore, + States: []*commonv1pb.StateItem{ + {Key: keys[0], Value: []byte("2"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[0]}}, + }, + }) + if err != nil { + return fmt.Errorf("failed to update value 0: %w", err) + } + + // Check the first state + etags[0], err = retrieveState(0, retrieveStateOpts{expectValue: "2", expectEtagNotEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 0: %w", err) + } + + // Updating with wrong etag should fail with 409 status code + _, err = grpcClient.SaveState(context.Background(), &runtimev1pb.SaveStateRequest{ + StoreName: statestore, + States: []*commonv1pb.StateItem{ + {Key: keys[1], Value: []byte("2"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: badEtag}}, + }, + }) + if status.Code(err) != codes.Aborted { + return fmt.Errorf("expected gRPC error with code Aborted, but got err: %v", err) + } + + // Value should not have changed + _, err = retrieveState(1, retrieveStateOpts{expectValue: "1", expectEtagEqual: etags[1]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 1: %w", err) + } + + // Bulk update with all valid etags + _, err = grpcClient.SaveState(context.Background(), &runtimev1pb.SaveStateRequest{ + StoreName: statestore, + States: []*commonv1pb.StateItem{ + {Key: keys[0], Value: []byte("3"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[0]}}, + {Key: keys[1], Value: []byte("3"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[1]}}, + {Key: keys[2], Value: []byte("3"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[2]}}, + }, + }) + if err != nil { + return fmt.Errorf("failed to update bulk values: %w", err) + } + + // Retrieve the three values to confirm they're updated + etags[0], err = retrieveState(0, retrieveStateOpts{expectValue: "3", expectEtagNotEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 0: %w", err) + } + etags[1], err = retrieveState(1, retrieveStateOpts{expectValue: "3", expectEtagNotEqual: etags[1]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 1: %w", err) + } + etags[2], err = retrieveState(2, retrieveStateOpts{expectValue: "3", expectEtagNotEqual: etags[2]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 2: %w", err) + } + + // Bulk update with one etag incorrect + _, err = grpcClient.SaveState(context.Background(), &runtimev1pb.SaveStateRequest{ + StoreName: statestore, + States: []*commonv1pb.StateItem{ + {Key: keys[0], Value: []byte("4"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: badEtag}}, + {Key: keys[1], Value: []byte("4"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[1]}}, + {Key: keys[2], Value: []byte("4"), Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[2]}}, + }, + }) + if status.Code(err) != codes.Aborted { + return fmt.Errorf("expected gRPC error with code Aborted, but got err: %v", err) + } + + // Retrieve the three values to confirm only the last two are updated + _, err = retrieveState(0, retrieveStateOpts{expectValue: "3", expectEtagEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 0: %w", err) + } + etags[1], err = retrieveState(1, retrieveStateOpts{expectValue: "4", expectEtagNotEqual: etags[1]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 1: %w", err) + } + etags[2], err = retrieveState(2, retrieveStateOpts{expectValue: "4", expectEtagNotEqual: etags[2]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 2: %w", err) + } + + // Delete single item with incorrect etag + _, err = grpcClient.DeleteState(context.Background(), &runtimev1pb.DeleteStateRequest{ + StoreName: statestore, + Key: keys[0], + Metadata: pkMetadata, + Etag: &commonv1pb.Etag{Value: badEtag}, + }) + if status.Code(err) != codes.Aborted { + return fmt.Errorf("expected gRPC error with code Aborted, but got err: %v", err) + } + + // Value should not have changed + _, err = retrieveState(0, retrieveStateOpts{expectValue: "3", expectEtagEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check updated value for state 0: %w", err) + } + + // Bulk delete with two etags incorrect + _, err = grpcClient.DeleteBulkState(context.Background(), &runtimev1pb.DeleteBulkStateRequest{ + StoreName: statestore, + States: []*commonv1pb.StateItem{ + {Key: keys[0], Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: badEtag}}, + {Key: keys[1], Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: badEtag}}, + {Key: keys[2], Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[2]}}, + }, + }) + if status.Code(err) != codes.Aborted { + return fmt.Errorf("expected gRPC error with code Aborted, but got err: %v", err) + } + + // Validate items 0 and 1 are the only ones still existing + _, err = retrieveState(0, retrieveStateOpts{expectValue: "3", expectEtagEqual: etags[0]}) + if err != nil { + return fmt.Errorf("failed to check value for state 0 after not deleting it: %w", err) + } + _, err = retrieveState(1, retrieveStateOpts{expectValue: "4", expectEtagEqual: etags[1]}) + if err != nil { + return fmt.Errorf("failed to check value for state 1 after not deleting it: %w", err) + } + _, err = retrieveState(2, retrieveStateOpts{expectNotFound: true}) + if err != nil { + return fmt.Errorf("failed to check value for state 2 after deleting it: %w", err) + } + + // Delete the remaining items + _, err = grpcClient.DeleteBulkState(context.Background(), &runtimev1pb.DeleteBulkStateRequest{ + StoreName: statestore, + States: []*commonv1pb.StateItem{ + {Key: keys[0], Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[0]}}, + {Key: keys[1], Metadata: pkMetadata, Etag: &commonv1pb.Etag{Value: etags[1]}}, + }, + }) + if err != nil { + return fmt.Errorf("failed to delete bulk values: %w", err) + } + + return nil +} + +// Returns a HTTP handler for functions that return an error +func testFnHandler(testFn func(statestore string) error) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + log.Printf("Processing request for %s", r.URL.RequestURI()) + + err := testFn(mux.Vars(r)["statestore"]) + if err != nil { + w.Header().Add("content-type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]any{ + "error": err.Error(), + }) + return + } + + w.WriteHeader(http.StatusNoContent) + } } // appRouter initializes restful api router @@ -798,6 +1228,8 @@ func appRouter() http.Handler { router.HandleFunc("/", indexHandler).Methods("GET") router.HandleFunc("/test/http/{command}/{statestore}", httpHandler).Methods("POST") router.HandleFunc("/test/grpc/{command}/{statestore}", grpcHandler).Methods("POST") + router.HandleFunc("/test-etag/http/{statestore}", testFnHandler(etagTestHTTP)).Methods("POST") + router.HandleFunc("/test-etag/grpc/{statestore}", testFnHandler(etagTestGRPC)).Methods("POST") router.Use(mux.CORSMethodMiddleware(router)) return router diff --git a/tests/e2e/stateapp/stateapp_test.go b/tests/e2e/stateapp/stateapp_test.go index 8bcaa123ac2..959c1d2c116 100644 --- a/tests/e2e/stateapp/stateapp_test.go +++ b/tests/e2e/stateapp/stateapp_test.go @@ -19,17 +19,20 @@ package stateapp_e2e import ( "encoding/json" "fmt" + "net/http" "os" "reflect" "strings" "testing" - "github.com/dapr/dapr/tests/e2e/utils" - kube "github.com/dapr/dapr/tests/platforms/kubernetes" - "github.com/dapr/dapr/tests/runner" guuid "github.com/google/uuid" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" apiv1 "k8s.io/api/core/v1" + + "github.com/dapr/dapr/tests/e2e/utils" + kube "github.com/dapr/dapr/tests/platforms/kubernetes" + "github.com/dapr/dapr/tests/runner" ) const ( @@ -153,15 +156,10 @@ func generateTestCases(isHTTP bool) []testCase { if isHTTP { protocol = "http" } - // Just for readability - emptyRequest := requestResponse{ - nil, - } // Just for readability - emptyResponse := requestResponse{ - nil, - } + emptyRequest := requestResponse{} + emptyResponse := requestResponse{} testCase1Key := guuid.New().String() testCase1Value := "The best song ever is 'Highwayman' by 'The Highwaymen'." @@ -307,10 +305,9 @@ func generateStateTransactionCases(protocolType string) testStateTransactionCase testCase1Key, testCase2Key := guuid.New().String()+protocolType, guuid.New().String()+protocolType testCase1Value := "The best song ever is 'Highwayman' by 'The Highwaymen'." testCase2Value := "Hello World" + // Just for readability - emptyResponse := requestResponse{ - nil, - } + emptyResponse := requestResponse{} testStateTransactionCase := testStateTransactionCase{ []stateTransactionTestStep{ @@ -692,3 +689,27 @@ func TestQueryStateStore(t *testing.T) { } } } + +func TestEtags(t *testing.T) { + externalURL := tr.Platform.AcquireAppExternalURL(appName) + require.NotEmpty(t, externalURL, "external URL must not be empty!") + + testCases := []struct { + protocol string + }{ + {protocol: "http"}, + {protocol: "grpc"}, + } + + // Now we are ready to run the actual tests + for _, tt := range testCases { + t.Run(fmt.Sprintf("Test Etags using %s protocol", tt.protocol), func(t *testing.T) { + url := strings.TrimSpace(fmt.Sprintf("%s/test-etag/%s/statestore", externalURL, tt.protocol)) + resp, status, err := utils.HTTPPostWithStatus(url, nil) + require.NoError(t, err) + + // The test passes with 204 if there's no error + assert.Equalf(t, http.StatusNoContent, status, "Test failed. Body is: %q", string(resp)) + }) + } +} diff --git a/tests/e2e/utils/helpers.go b/tests/e2e/utils/helpers.go index 9008bf837cd..7f4d3371fff 100644 --- a/tests/e2e/utils/helpers.go +++ b/tests/e2e/utils/helpers.go @@ -30,8 +30,8 @@ const ( // SimpleKeyValue can be used to simplify code, providing simple key-value pairs. type SimpleKeyValue struct { - Key interface{} - Value interface{} + Key any + Value any } // StateTransactionKeyValue is a key-value pair with an operation type.