Skip to content

Commit cc80a66

Browse files
authored
Merge branch 'main' into dapr-state-store-clickhouse
2 parents f141667 + 2aea319 commit cc80a66

File tree

4 files changed

+173
-30
lines changed

4 files changed

+173
-30
lines changed

pubsub/pulsar/metadata.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,25 @@ import (
2020
)
2121

2222
type pulsarMetadata struct {
23-
Host string `mapstructure:"host"`
24-
ConsumerID string `mapstructure:"consumerID"`
25-
EnableTLS bool `mapstructure:"enableTLS"`
26-
DisableBatching bool `mapstructure:"disableBatching"`
27-
BatchingMaxPublishDelay time.Duration `mapstructure:"batchingMaxPublishDelay"`
28-
BatchingMaxSize uint `mapstructure:"batchingMaxSize"`
29-
BatchingMaxMessages uint `mapstructure:"batchingMaxMessages"`
30-
Tenant string `mapstructure:"tenant"`
31-
Namespace string `mapstructure:"namespace"`
32-
Persistent bool `mapstructure:"persistent"`
33-
RedeliveryDelay time.Duration `mapstructure:"redeliveryDelay"`
34-
internalTopicSchemas map[string]schemaMetadata `mapstructure:"-"`
35-
PublicKey string `mapstructure:"publicKey"`
36-
PrivateKey string `mapstructure:"privateKey"`
37-
Keys string `mapstructure:"keys"`
38-
MaxConcurrentHandlers uint `mapstructure:"maxConcurrentHandlers"`
39-
ReceiverQueueSize int `mapstructure:"receiverQueueSize"`
40-
41-
Token string `mapstructure:"token"`
23+
Host string `mapstructure:"host"`
24+
ConsumerID string `mapstructure:"consumerID"`
25+
EnableTLS bool `mapstructure:"enableTLS"`
26+
DisableBatching bool `mapstructure:"disableBatching"`
27+
BatchingMaxPublishDelay time.Duration `mapstructure:"batchingMaxPublishDelay"`
28+
BatchingMaxSize uint `mapstructure:"batchingMaxSize"`
29+
BatchingMaxMessages uint `mapstructure:"batchingMaxMessages"`
30+
Tenant string `mapstructure:"tenant"`
31+
Namespace string `mapstructure:"namespace"`
32+
Persistent bool `mapstructure:"persistent"`
33+
RedeliveryDelay time.Duration `mapstructure:"redeliveryDelay"`
34+
internalTopicSchemas map[string]schemaMetadata `mapstructure:"-"`
35+
PublicKey string `mapstructure:"publicKey"`
36+
PrivateKey string `mapstructure:"privateKey"`
37+
Keys string `mapstructure:"keys"`
38+
MaxConcurrentHandlers uint `mapstructure:"maxConcurrentHandlers"`
39+
ReceiverQueueSize int `mapstructure:"receiverQueueSize"`
40+
SubscriptionType string `mapstructure:"subscribeType"`
41+
Token string `mapstructure:"token"`
4242
oauth2.ClientCredentialsMetadata `mapstructure:",squash"`
4343
}
4444

pubsub/pulsar/metadata.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,13 @@ metadata:
183183
Sets the size of the consumer receive queue.
184184
Controls how many messages can be accumulated by the consumer before it is explicitly called to read messages by Dapr.
185185
default: '"1000"'
186-
example: '"1000"'
186+
example: '"1000"'
187+
- name: subscribeType
188+
type: string
189+
description: |
190+
Pulsar supports four subscription types:"shared", "exclusive", "failover", "key_shared".
191+
default: '"shared"'
192+
example: '"exclusive"'
193+
url:
194+
title: "Pulsar Subscription Types"
195+
url: "https://pulsar.apache.org/docs/3.0.x/concepts-messaging/#subscription-types"

pubsub/pulsar/pulsar.go

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ func parsePulsarMetadata(meta pubsub.Metadata) (*pulsarMetadata, error) {
138138
return nil, errors.New("pulsar error: missing pulsar host")
139139
}
140140

141+
var err error
142+
m.SubscriptionType, err = parseSubscriptionType(meta.Properties[subscribeTypeKey])
143+
if err != nil {
144+
return nil, errors.New("invalid subscription type. Accepted values are `exclusive`, `shared`, `failover` and `key_shared`")
145+
}
146+
141147
for k, v := range meta.Properties {
142148
switch {
143149
case strings.HasSuffix(k, topicJSONSchemaIdentifier):
@@ -170,10 +176,8 @@ func (p *Pulsar) Init(ctx context.Context, metadata pubsub.Metadata) error {
170176
return err
171177
}
172178
pulsarURL := m.Host
173-
if !strings.HasPrefix(m.Host, "http://") &&
174-
!strings.HasPrefix(m.Host, "https://") {
175-
pulsarURL = fmt.Sprintf("%s%s", pulsarPrefix, m.Host)
176-
}
179+
180+
pulsarURL = sanitiseURL(pulsarURL)
177181
options := pulsar.ClientOptions{
178182
URL: pulsarURL,
179183
OperationTimeout: 30 * time.Second,
@@ -226,6 +230,23 @@ func (p *Pulsar) Init(ctx context.Context, metadata pubsub.Metadata) error {
226230
return nil
227231
}
228232

233+
func sanitiseURL(pulsarURL string) string {
234+
prefixes := []string{"pulsar+ssl://", "pulsar://", "http://", "https://"}
235+
236+
hasPrefix := false
237+
for _, prefix := range prefixes {
238+
if strings.HasPrefix(pulsarURL, prefix) {
239+
hasPrefix = true
240+
break
241+
}
242+
}
243+
244+
if !hasPrefix {
245+
pulsarURL = fmt.Sprintf("%s%s", pulsarPrefix, pulsarURL)
246+
}
247+
return pulsarURL
248+
}
249+
229250
func (p *Pulsar) useProducerEncryption() bool {
230251
return p.metadata.PublicKey != "" && p.metadata.Keys != ""
231252
}
@@ -370,11 +391,22 @@ func parsePublishMetadata(req *pubsub.PublishRequest, schema schemaMetadata) (
370391
return msg, nil
371392
}
372393

373-
// default: shared
374-
func getSubscribeType(metadata map[string]string) pulsar.SubscriptionType {
394+
func parseSubscriptionType(in string) (string, error) {
395+
subsType := strings.ToLower(in)
396+
switch subsType {
397+
case subscribeTypeExclusive, subscribeTypeFailover, subscribeTypeShared, subscribeTypeKeyShared:
398+
return subsType, nil
399+
case "":
400+
return subscribeTypeShared, nil
401+
default:
402+
return "", fmt.Errorf("invalid subscription type: %s", subsType)
403+
}
404+
}
405+
406+
// getSubscribeType doesn't do extra validations, because they were done in parseSubscriptionType.
407+
func getSubscribeType(subsTypeStr string) pulsar.SubscriptionType {
375408
var subsType pulsar.SubscriptionType
376409

377-
subsTypeStr := strings.ToLower(metadata[subscribeTypeKey])
378410
switch subsTypeStr {
379411
case subscribeTypeExclusive:
380412
subsType = pulsar.Exclusive
@@ -384,8 +416,6 @@ func getSubscribeType(metadata map[string]string) pulsar.SubscriptionType {
384416
subsType = pulsar.Shared
385417
case subscribeTypeKeyShared:
386418
subsType = pulsar.KeyShared
387-
default:
388-
subsType = pulsar.Shared
389419
}
390420

391421
return subsType
@@ -400,15 +430,27 @@ func (p *Pulsar) Subscribe(ctx context.Context, req pubsub.SubscribeRequest, han
400430

401431
topic := p.formatTopic(req.Topic)
402432

433+
subscribeType := p.metadata.SubscriptionType
434+
if s, exists := req.Metadata[subscribeTypeKey]; exists {
435+
subscribeType = s
436+
}
437+
403438
options := pulsar.ConsumerOptions{
404439
Topic: topic,
405440
SubscriptionName: p.metadata.ConsumerID,
406-
Type: getSubscribeType(req.Metadata),
441+
Type: getSubscribeType(subscribeType),
407442
MessageChannel: channel,
408443
NackRedeliveryDelay: p.metadata.RedeliveryDelay,
409444
ReceiverQueueSize: p.metadata.ReceiverQueueSize,
410445
}
411446

447+
// Handle KeySharedPolicy for key_shared subscription type
448+
if options.Type == pulsar.KeyShared {
449+
options.KeySharedPolicy = &pulsar.KeySharedPolicy{
450+
Mode: pulsar.KeySharedPolicyModeAutoSplit,
451+
}
452+
}
453+
412454
if p.useConsumerEncryption() {
413455
var reader crypto.KeyReader
414456
if isValidPEM(p.metadata.PublicKey) {
@@ -430,6 +472,7 @@ func (p *Pulsar) Subscribe(ctx context.Context, req pubsub.SubscribeRequest, han
430472
p.logger.Debugf("Could not subscribe to %s, full topic name in pulsar is %s", req.Topic, topic)
431473
return err
432474
}
475+
p.logger.Debugf("Subscribed to '%s'(%s) with type '%s'", req.Topic, topic, subscribeType)
433476

434477
p.wg.Add(2)
435478
listenCtx, cancel := context.WithCancel(ctx)

pubsub/pulsar/pulsar_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,73 @@ func TestParsePulsarMetadata(t *testing.T) {
4848
assert.Equal(t, uint(200), meta.BatchingMaxMessages)
4949
assert.Equal(t, uint(333), meta.MaxConcurrentHandlers)
5050
assert.Empty(t, meta.internalTopicSchemas)
51+
assert.Equal(t, "shared", meta.SubscriptionType)
52+
}
53+
54+
func TestParsePulsarMetadataSubscriptionType(t *testing.T) {
55+
tt := []struct {
56+
name string
57+
subscribeType string
58+
expected string
59+
err bool
60+
}{
61+
{
62+
name: "test valid subscribe type - key_shared",
63+
subscribeType: "key_shared",
64+
expected: "key_shared",
65+
err: false,
66+
},
67+
{
68+
name: "test valid subscribe type - shared",
69+
subscribeType: "shared",
70+
expected: "shared",
71+
err: false,
72+
},
73+
{
74+
name: "test valid subscribe type - failover",
75+
subscribeType: "failover",
76+
expected: "failover",
77+
err: false,
78+
},
79+
{
80+
name: "test valid subscribe type - exclusive",
81+
subscribeType: "exclusive",
82+
expected: "exclusive",
83+
err: false,
84+
},
85+
{
86+
name: "test valid subscribe type - empty",
87+
subscribeType: "",
88+
expected: "shared",
89+
err: false,
90+
},
91+
{
92+
name: "test invalid subscribe type",
93+
subscribeType: "invalid",
94+
err: true,
95+
},
96+
}
97+
98+
for _, tc := range tt {
99+
t.Run(tc.name, func(t *testing.T) {
100+
m := pubsub.Metadata{}
101+
102+
m.Properties = map[string]string{
103+
"host": "a",
104+
"subscribeType": tc.subscribeType,
105+
}
106+
meta, err := parsePulsarMetadata(m)
107+
108+
if tc.err {
109+
require.Error(t, err)
110+
assert.Nil(t, meta)
111+
return
112+
}
113+
114+
require.NoError(t, err)
115+
assert.Equal(t, tc.expected, meta.SubscriptionType)
116+
})
117+
}
51118
}
52119

53120
func TestParsePulsarSchemaMetadata(t *testing.T) {
@@ -328,3 +395,27 @@ func TestEncryptionKeys(t *testing.T) {
328395
assert.False(t, r)
329396
})
330397
}
398+
399+
func TestSanitiseURL(t *testing.T) {
400+
tests := []struct {
401+
name string
402+
input string
403+
expected string
404+
}{
405+
{"With pulsar+ssl prefix", "pulsar+ssl://localhost:6650", "pulsar+ssl://localhost:6650"},
406+
{"With pulsar prefix", "pulsar://localhost:6650", "pulsar://localhost:6650"},
407+
{"With http prefix", "http://localhost:6650", "http://localhost:6650"},
408+
{"With https prefix", "https://localhost:6650", "https://localhost:6650"},
409+
{"Without prefix", "localhost:6650", "pulsar://localhost:6650"},
410+
{"Empty string", "", "pulsar://"},
411+
}
412+
413+
for _, test := range tests {
414+
t.Run(test.name, func(t *testing.T) {
415+
actual := sanitiseURL(test.input)
416+
if actual != test.expected {
417+
t.Errorf("sanitiseURL(%q) = %q; want %q", test.input, actual, test.expected)
418+
}
419+
})
420+
}
421+
}

0 commit comments

Comments
 (0)