Azure Storage Queues: Make polling interval configurable (#2781)

Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
Alessandro (Ale) Segala 2023-04-14 16:08:18 -07:00 committed by GitHub
parent c6546ffe7d
commit e59e03eb32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 26 deletions

View File

@ -44,6 +44,12 @@ metadata:
example: | example: |
"http://127.0.0.1:10001" "http://127.0.0.1:10001"
"https://accountName.queue.example.com" "https://accountName.queue.example.com"
- name: "pollingInterval"
type: duration
description: |
Set the interval to poll Azure Storage Queues for new messages
example: '"30s"'
default: '"10s"'
- name: "ttlInSeconds" - name: "ttlInSeconds"
type: number type: number
description: | description: |
@ -74,10 +80,9 @@ metadata:
output: true output: true
input: false input: false
- name: "visibilityTimeout" - name: "visibilityTimeout"
type: number type: duration
description: | description: |
Allows setting a custom queue visibility timeout to avoid immediate retrying of recently-failed messages. Allows setting a custom queue visibility timeout to avoid immediate retrying of recently-failed messages.
Value is formatted as a Go duration, with a 30s default.
example: '1m' example: '1m'
default: '30s' default: '30s'
binding: binding:

View File

@ -35,7 +35,9 @@ import (
) )
const ( const (
defaultTTL = time.Minute * 10 defaultTTL = 10 * time.Minute
defaultVisibilityTimeout = 30 * time.Second
defaultPollingInterval = 10 * time.Second
) )
type consumer struct { type consumer struct {
@ -56,6 +58,7 @@ type AzureQueueHelper struct {
logger logger.Logger logger logger.Logger
decodeBase64 bool decodeBase64 bool
encodeBase64 bool encodeBase64 bool
pollingInterval time.Duration
visibilityTimeout time.Duration visibilityTimeout time.Duration
} }
@ -105,6 +108,7 @@ func (d *AzureQueueHelper) Init(ctx context.Context, meta bindings.Metadata) (*s
d.decodeBase64 = m.DecodeBase64 d.decodeBase64 = m.DecodeBase64
d.encodeBase64 = m.EncodeBase64 d.encodeBase64 = m.EncodeBase64
d.pollingInterval = m.PollingInterval
d.visibilityTimeout = *m.VisibilityTimeout d.visibilityTimeout = *m.VisibilityTimeout
d.queueClient = queueServiceClient.NewQueueClient(m.QueueName) d.queueClient = queueServiceClient.NewQueueClient(m.QueueName)
@ -151,9 +155,9 @@ func (d *AzureQueueHelper) Read(ctx context.Context, consumer *consumer) error {
return err return err
} }
if len(res.Messages) == 0 { if len(res.Messages) == 0 {
// Queue was empty so back off by 10 seconds before trying again // Queue was empty so back off seconds before trying again
select { select {
case <-time.After(10 * time.Second): case <-time.After(d.pollingInterval):
case <-ctx.Done(): case <-ctx.Done():
} }
return nil return nil
@ -222,6 +226,7 @@ type storageQueuesMetadata struct {
AccountKey string AccountKey string
DecodeBase64 bool DecodeBase64 bool
EncodeBase64 bool EncodeBase64 bool
PollingInterval time.Duration `mapstructure:"pollingInterval"`
TTL *time.Duration `mapstructure:"ttlInSeconds"` TTL *time.Duration `mapstructure:"ttlInSeconds"`
VisibilityTimeout *time.Duration VisibilityTimeout *time.Duration
} }
@ -257,7 +262,8 @@ func (a *AzureStorageQueues) Init(ctx context.Context, metadata bindings.Metadat
func parseMetadata(meta bindings.Metadata) (*storageQueuesMetadata, error) { func parseMetadata(meta bindings.Metadata) (*storageQueuesMetadata, error) {
m := storageQueuesMetadata{ m := storageQueuesMetadata{
VisibilityTimeout: ptr.Of(time.Second * 30), PollingInterval: defaultPollingInterval,
VisibilityTimeout: ptr.Of(defaultVisibilityTimeout),
} }
contribMetadata.DecodeMetadata(meta.Properties, &m) contribMetadata.DecodeMetadata(meta.Properties, &m)
@ -281,6 +287,10 @@ func parseMetadata(meta bindings.Metadata) (*storageQueuesMetadata, error) {
m.AccountKey = val m.AccountKey = val
} }
if m.PollingInterval < (100 * time.Millisecond) {
return nil, errors.New("invalid value for 'pollingInterval': must be greater than 100ms")
}
ttl, ok, err := contribMetadata.TryGetTTL(meta.Properties) ttl, ok, err := contribMetadata.TryGetTTL(meta.Properties)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -22,6 +22,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/dapr/components-contrib/bindings" "github.com/dapr/components-contrib/bindings"
"github.com/dapr/components-contrib/metadata" "github.com/dapr/components-contrib/metadata"
@ -96,13 +97,13 @@ func TestWriteQueue(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
r := bindings.InvokeRequest{Data: []byte("This is my message")} r := bindings.InvokeRequest{Data: []byte("This is my message")}
_, err = a.Invoke(context.Background(), &r) _, err = a.Invoke(context.Background(), &r)
assert.Nil(t, err) require.NoError(t, err)
assert.NoError(t, a.Close()) assert.NoError(t, a.Close())
} }
@ -118,13 +119,13 @@ func TestWriteWithTTLInQueue(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1", metadata.TTLMetadataKey: "1"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1", metadata.TTLMetadataKey: "1"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
r := bindings.InvokeRequest{Data: []byte("This is my message")} r := bindings.InvokeRequest{Data: []byte("This is my message")}
_, err = a.Invoke(context.Background(), &r) _, err = a.Invoke(context.Background(), &r)
assert.Nil(t, err) require.NoError(t, err)
assert.NoError(t, a.Close()) assert.NoError(t, a.Close())
} }
@ -140,7 +141,7 @@ func TestWriteWithTTLInWrite(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1", metadata.TTLMetadataKey: "1"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1", metadata.TTLMetadataKey: "1"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
r := bindings.InvokeRequest{ r := bindings.InvokeRequest{
Data: []byte("This is my message"), Data: []byte("This is my message"),
@ -149,7 +150,7 @@ func TestWriteWithTTLInWrite(t *testing.T) {
_, err = a.Invoke(context.Background(), &r) _, err = a.Invoke(context.Background(), &r)
assert.Nil(t, err) require.NoError(t, err)
assert.NoError(t, a.Close()) assert.NoError(t, a.Close())
} }
@ -162,13 +163,13 @@ func TestWriteWithTTLInWrite(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
r := bindings.InvokeRequest{Data: []byte("This is my message")} r := bindings.InvokeRequest{Data: []byte("This is my message")}
err = a.Write(&r) err = a.Write(&r)
assert.Nil(t, err) require.NoError(t, err)
} */ } */
func TestReadQueue(t *testing.T) { func TestReadQueue(t *testing.T) {
@ -181,14 +182,14 @@ func TestReadQueue(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
r := bindings.InvokeRequest{Data: []byte("This is my message")} r := bindings.InvokeRequest{Data: []byte("This is my message")}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
_, err = a.Invoke(ctx, &r) _, err = a.Invoke(ctx, &r)
assert.Nil(t, err) require.NoError(t, err)
received := 0 received := 0
handler := func(ctx context.Context, data *bindings.ReadResponse) ([]byte, error) { handler := func(ctx context.Context, data *bindings.ReadResponse) ([]byte, error) {
@ -223,14 +224,14 @@ func TestReadQueueDecode(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1", "decodeBase64": "true"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1", "decodeBase64": "true"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
r := bindings.InvokeRequest{Data: []byte("VGhpcyBpcyBteSBtZXNzYWdl")} r := bindings.InvokeRequest{Data: []byte("VGhpcyBpcyBteSBtZXNzYWdl")}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
_, err = a.Invoke(ctx, &r) _, err = a.Invoke(ctx, &r)
assert.Nil(t, err) require.NoError(t, err)
received := 0 received := 0
handler := func(ctx context.Context, data *bindings.ReadResponse) ([]byte, error) { handler := func(ctx context.Context, data *bindings.ReadResponse) ([]byte, error) {
@ -263,13 +264,13 @@ func TestReadQueueDecode(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
r := bindings.InvokeRequest{Data: []byte("This is my message")} r := bindings.InvokeRequest{Data: []byte("This is my message")}
err = a.Write(&r) err = a.Write(&r)
assert.Nil(t, err) require.NoError(t, err)
var handler = func(data *bindings.ReadResponse) ([]byte, error) { var handler = func(data *bindings.ReadResponse) ([]byte, error) {
s := string(data.Data) s := string(data.Data)
@ -294,7 +295,7 @@ func TestReadQueueNoMessage(t *testing.T) {
m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"} m.Properties = map[string]string{"storageAccessKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "queue": "queue1", "storageAccount": "devstoreaccount1"}
err := a.Init(context.Background(), m) err := a.Init(context.Background(), m)
assert.Nil(t, err) require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
received := 0 received := 0
@ -323,6 +324,7 @@ func TestParseMetadata(t *testing.T) {
// expectedAccountKey string // expectedAccountKey string
expectedQueueName string expectedQueueName string
expectedQueueEndpointURL string expectedQueueEndpointURL string
expectedPollingInterval time.Duration
expectedTTL *time.Duration expectedTTL *time.Duration
expectedVisibilityTimeout *time.Duration expectedVisibilityTimeout *time.Duration
}{ }{
@ -332,7 +334,8 @@ func TestParseMetadata(t *testing.T) {
// expectedAccountKey: "myKey", // expectedAccountKey: "myKey",
expectedQueueName: "queue1", expectedQueueName: "queue1",
expectedQueueEndpointURL: "", expectedQueueEndpointURL: "",
expectedVisibilityTimeout: ptr.Of(30 * time.Second), expectedPollingInterval: defaultPollingInterval,
expectedVisibilityTimeout: ptr.Of(defaultVisibilityTimeout),
}, },
{ {
name: "Accout, key, and endpoint", name: "Accout, key, and endpoint",
@ -340,7 +343,8 @@ func TestParseMetadata(t *testing.T) {
// expectedAccountKey: "myKey", // expectedAccountKey: "myKey",
expectedQueueName: "queue1", expectedQueueName: "queue1",
expectedQueueEndpointURL: "https://foo.example.com:10001", expectedQueueEndpointURL: "https://foo.example.com:10001",
expectedVisibilityTimeout: ptr.Of(30 * time.Second), expectedPollingInterval: defaultPollingInterval,
expectedVisibilityTimeout: ptr.Of(defaultVisibilityTimeout),
}, },
{ {
name: "Empty TTL", name: "Empty TTL",
@ -349,7 +353,8 @@ func TestParseMetadata(t *testing.T) {
expectedQueueName: "queue1", expectedQueueName: "queue1",
expectedQueueEndpointURL: "", expectedQueueEndpointURL: "",
expectedTTL: ptr.Of(time.Duration(0)), expectedTTL: ptr.Of(time.Duration(0)),
expectedVisibilityTimeout: ptr.Of(30 * time.Second), expectedPollingInterval: defaultPollingInterval,
expectedVisibilityTimeout: ptr.Of(defaultVisibilityTimeout),
}, },
{ {
name: "With TTL", name: "With TTL",
@ -358,14 +363,25 @@ func TestParseMetadata(t *testing.T) {
expectedQueueName: "queue1", expectedQueueName: "queue1",
expectedTTL: &oneSecondDuration, expectedTTL: &oneSecondDuration,
expectedQueueEndpointURL: "", expectedQueueEndpointURL: "",
expectedVisibilityTimeout: ptr.Of(30 * time.Second), expectedPollingInterval: defaultPollingInterval,
expectedVisibilityTimeout: ptr.Of(defaultVisibilityTimeout),
}, },
{ {
name: "With visibility timeout", name: "With visibility timeout",
properties: map[string]string{"accessKey": "myKey", "storageAccountQueue": "queue1", "storageAccount": "devstoreaccount1", "visibilityTimeout": "5s"}, properties: map[string]string{"accessKey": "myKey", "storageAccountQueue": "queue1", "storageAccount": "devstoreaccount1", "visibilityTimeout": "5s"},
expectedQueueName: "queue1", expectedQueueName: "queue1",
expectedPollingInterval: defaultPollingInterval,
expectedVisibilityTimeout: ptr.Of(5 * time.Second), expectedVisibilityTimeout: ptr.Of(5 * time.Second),
}, },
{
name: "With polling interval",
properties: map[string]string{"accessKey": "myKey", "storageAccountQueue": "queue1", "storageAccount": "devstoreaccount1", "pollingInterval": "2s"},
// expectedAccountKey: "myKey",
expectedQueueName: "queue1",
expectedQueueEndpointURL: "",
expectedPollingInterval: 2 * time.Second,
expectedVisibilityTimeout: ptr.Of(defaultVisibilityTimeout),
},
} }
for _, tt := range testCases { for _, tt := range testCases {
@ -375,7 +391,7 @@ func TestParseMetadata(t *testing.T) {
meta, err := parseMetadata(m) meta, err := parseMetadata(m)
assert.Nil(t, err) require.NoError(t, err)
// assert.Equal(t, tt.expectedAccountKey, meta.AccountKey) // assert.Equal(t, tt.expectedAccountKey, meta.AccountKey)
assert.Equal(t, tt.expectedQueueName, meta.QueueName) assert.Equal(t, tt.expectedQueueName, meta.QueueName)
assert.Equal(t, tt.expectedTTL, meta.TTL) assert.Equal(t, tt.expectedTTL, meta.TTL)
@ -383,6 +399,20 @@ func TestParseMetadata(t *testing.T) {
assert.Equal(t, tt.expectedVisibilityTimeout, meta.VisibilityTimeout) assert.Equal(t, tt.expectedVisibilityTimeout, meta.VisibilityTimeout)
}) })
} }
t.Run("invalid pollingInterval", func(t *testing.T) {
m := bindings.Metadata{Base: metadata.Base{
Properties: map[string]string{
"accessKey": "myKey",
"storageAccountQueue": "queue1",
"storageAccount": "devstoreaccount1",
"pollingInterval": "-1s",
},
}}
_, err := parseMetadata(m)
require.Error(t, err)
})
} }
func TestParseMetadataWithInvalidTTL(t *testing.T) { func TestParseMetadataWithInvalidTTL(t *testing.T) {