github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/pubsub/azuresb/azuresb_test.go (about) 1 // Copyright 2018 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package azuresb 15 16 import ( 17 "context" 18 "fmt" 19 "os" 20 "strings" 21 "sync/atomic" 22 "testing" 23 24 "gocloud.dev/internal/testing/setup" 25 "gocloud.dev/pubsub" 26 "gocloud.dev/pubsub/driver" 27 "gocloud.dev/pubsub/drivertest" 28 29 servicebus "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" 30 "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/admin" 31 ) 32 33 var ( 34 // See docs below on how to provision an Azure Service Bus Namespace and obtaining the connection string. 35 // https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-get-started-with-queues 36 connString = os.Getenv("SERVICEBUS_CONNECTION_STRING") 37 ) 38 39 const ( 40 nonexistentTopicName = "nonexistent-topic" 41 42 // Try to keep the entity name under Azure limits. 43 // https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-quotas 44 // says 50, but there appears to be some additional overhead. 40 works. 45 maxNameLen = 40 46 ) 47 48 type harness struct { 49 adminClient *admin.Client 50 sbClient *servicebus.Client 51 numTopics uint32 // atomic 52 numSubs uint32 // atomic 53 closer func() 54 autodelete bool 55 topics map[driver.Topic]string 56 } 57 58 func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) { 59 if connString == "" { 60 return nil, fmt.Errorf("azuresb: test harness requires environment variable SERVICEBUS_CONNECTION_STRING to run") 61 } 62 adminClient, err := admin.NewClientFromConnectionString(connString, nil) 63 if err != nil { 64 return nil, err 65 } 66 sbClient, err := NewClientFromConnectionString(connString, nil) 67 if err != nil { 68 return nil, err 69 } 70 noop := func() {} 71 return &harness{ 72 adminClient: adminClient, 73 sbClient: sbClient, 74 closer: noop, 75 topics: map[driver.Topic]string{}, 76 }, nil 77 } 78 79 func newHarnessUsingAutodelete(ctx context.Context, t *testing.T) (drivertest.Harness, error) { 80 h, err := newHarness(ctx, t) 81 if err == nil { 82 h.(*harness).autodelete = true 83 } 84 return h, err 85 } 86 87 func (h *harness) CreateTopic(ctx context.Context, testName string) (dt driver.Topic, cleanup func(), err error) { 88 topicName := sanitize(fmt.Sprintf("%s-top-%d", testName, atomic.AddUint32(&h.numTopics, 1))) 89 if err := createTopic(ctx, topicName, h.adminClient, nil); err != nil { 90 return nil, nil, err 91 } 92 93 sbSender, err := NewSender(h.sbClient, topicName, nil) 94 dt, err = openTopic(ctx, sbSender, nil) 95 if err != nil { 96 return nil, nil, err 97 } 98 h.topics[dt] = topicName 99 cleanup = func() { 100 sbSender.Close(ctx) 101 deleteTopic(ctx, topicName, h.adminClient) 102 } 103 return dt, cleanup, nil 104 } 105 106 func (h *harness) MakeNonexistentTopic(ctx context.Context) (driver.Topic, error) { 107 sbSender, err := NewSender(h.sbClient, nonexistentTopicName, nil) 108 if err != nil { 109 return nil, err 110 } 111 dt, err := openTopic(ctx, sbSender, nil) 112 if err != nil { 113 return nil, err 114 } 115 h.topics[dt] = nonexistentTopicName 116 return dt, nil 117 } 118 119 func (h *harness) CreateSubscription(ctx context.Context, dt driver.Topic, testName string) (ds driver.Subscription, cleanup func(), err error) { 120 subName := sanitize(fmt.Sprintf("%s-sub-%d", testName, atomic.AddUint32(&h.numSubs, 1))) 121 topicName := h.topics[dt] 122 err = createSubscription(ctx, topicName, subName, h.adminClient, nil) 123 if err != nil { 124 return nil, nil, err 125 } 126 127 var opts servicebus.ReceiverOptions 128 if h.autodelete { 129 opts.ReceiveMode = servicebus.ReceiveModeReceiveAndDelete 130 } 131 sbReceiver, err := NewReceiver(h.sbClient, topicName, subName, &opts) 132 if err != nil { 133 return nil, nil, err 134 } 135 136 sopts := SubscriptionOptions{} 137 if h.autodelete { 138 sopts.ReceiveAndDelete = true 139 } 140 ds, err = openSubscription(ctx, h.sbClient, sbReceiver, &sopts) 141 if err != nil { 142 return nil, nil, err 143 } 144 145 cleanup = func() { 146 sbReceiver.Close(ctx) 147 deleteSubscription(ctx, topicName, subName, h.adminClient) 148 } 149 return ds, cleanup, nil 150 } 151 152 func (h *harness) MakeNonexistentSubscription(ctx context.Context) (driver.Subscription, func(), error) { 153 const topicName = "topic-for-nonexistent-sub" 154 _, cleanup, err := h.CreateTopic(ctx, topicName) 155 if err != nil { 156 return nil, nil, err 157 } 158 sbReceiver, err := NewReceiver(h.sbClient, topicName, "nonexistent-subscription", nil) 159 if err != nil { 160 return nil, cleanup, err 161 } 162 sub, err := openSubscription(ctx, h.sbClient, sbReceiver, nil) 163 return sub, cleanup, err 164 } 165 166 func (h *harness) Close() { 167 h.closer() 168 } 169 170 func (h *harness) MaxBatchSizes() (int, int) { return sendBatcherOpts.MaxBatchSize, 0 } 171 172 func (h *harness) SupportsMultipleSubscriptions() bool { return true } 173 174 // Please run the TestConformance with an extended timeout since each test needs to perform CRUD for ServiceBus Topics and Subscriptions. 175 // Example: C:\Go\bin\go.exe test -timeout 60s gocloud.dev/pubsub/azuresb -run ^TestConformance$ 176 func TestConformance(t *testing.T) { 177 if !*setup.Record { 178 t.Skip("replaying is not yet supported for Azure pubsub") 179 } 180 asTests := []drivertest.AsTest{sbAsTest{}} 181 drivertest.RunConformanceTests(t, newHarness, asTests) 182 } 183 184 func TestConformanceWithAutodelete(t *testing.T) { 185 if !*setup.Record { 186 t.Skip("replaying is not yet supported for Azure pubsub") 187 } 188 asTests := []drivertest.AsTest{sbAsTest{}} 189 drivertest.RunConformanceTests(t, newHarnessUsingAutodelete, asTests) 190 } 191 192 type sbAsTest struct{} 193 194 func (sbAsTest) Name() string { 195 return "azure" 196 } 197 198 func (sbAsTest) TopicCheck(topic *pubsub.Topic) error { 199 var t2 servicebus.Sender 200 if topic.As(&t2) { 201 return fmt.Errorf("cast succeeded for %T, want failure", &t2) 202 } 203 var t3 *servicebus.Sender 204 if !topic.As(&t3) { 205 return fmt.Errorf("cast failed for %T", &t3) 206 } 207 return nil 208 } 209 210 func (sbAsTest) SubscriptionCheck(sub *pubsub.Subscription) error { 211 var s2 servicebus.Receiver 212 if sub.As(&s2) { 213 return fmt.Errorf("cast succeeded for %T, want failure", &s2) 214 } 215 var s3 *servicebus.Receiver 216 if !sub.As(&s3) { 217 return fmt.Errorf("cast failed for %T", &s3) 218 } 219 return nil 220 } 221 222 func (sbAsTest) TopicErrorCheck(t *pubsub.Topic, err error) error { 223 return nil 224 } 225 226 func (sbAsTest) SubscriptionErrorCheck(s *pubsub.Subscription, err error) error { 227 return nil 228 } 229 230 func (sbAsTest) MessageCheck(m *pubsub.Message) error { 231 var m2 servicebus.ReceivedMessage 232 if m.As(&m2) { 233 return fmt.Errorf("cast succeeded for %T, want failure", &m2) 234 } 235 var m3 *servicebus.ReceivedMessage 236 if !m.As(&m3) { 237 return fmt.Errorf("cast failed for %T", &m3) 238 } 239 return nil 240 } 241 242 func (sbAsTest) BeforeSend(as func(interface{}) bool) error { 243 var m *servicebus.Message 244 if !as(&m) { 245 return fmt.Errorf("cast failed for %T", &m) 246 } 247 return nil 248 } 249 250 func (sbAsTest) AfterSend(as func(interface{}) bool) error { 251 return nil 252 } 253 254 func sanitize(s string) string { 255 // First trim some not-so-useful strings that are part of all test names. 256 s = strings.Replace(s, "TestConformance/Test", "", 1) 257 s = strings.Replace(s, "TestConformanceWithAutodelete/Test", "", 1) 258 s = strings.Replace(s, "/", "_", -1) 259 if len(s) > maxNameLen { 260 // Drop prefix, not suffix, because suffix includes something to make 261 // entities unique within a test. 262 s = s[len(s)-maxNameLen:] 263 } 264 return s 265 } 266 267 // createTopic ensures the existence of a Service Bus Topic on a given Namespace. 268 func createTopic(ctx context.Context, topicName string, adminClient *admin.Client, properties *admin.TopicProperties) error { 269 t, _ := adminClient.GetTopic(ctx, topicName, nil) 270 if t != nil { 271 _, _ = adminClient.DeleteTopic(ctx, topicName, nil) 272 } 273 opts := admin.CreateTopicOptions{ 274 Properties: properties, 275 } 276 _, err := adminClient.CreateTopic(ctx, topicName, &opts) 277 return err 278 } 279 280 // deleteTopic removes a Service Bus Topic on a given Namespace. 281 func deleteTopic(ctx context.Context, topicName string, adminClient *admin.Client) error { 282 t, _ := adminClient.GetTopic(ctx, topicName, nil) 283 if t != nil { 284 _, err := adminClient.DeleteTopic(ctx, topicName, nil) 285 return err 286 } 287 return nil 288 } 289 290 // createSubscription ensures the existence of a Service Bus Subscription on a given Namespace and Topic. 291 func createSubscription(ctx context.Context, topicName string, subscriptionName string, adminClient *admin.Client, properties *admin.SubscriptionProperties) error { 292 s, _ := adminClient.GetSubscription(ctx, topicName, subscriptionName, nil) 293 if s != nil { 294 _, _ = adminClient.DeleteSubscription(ctx, topicName, subscriptionName, nil) 295 } 296 opts := admin.CreateSubscriptionOptions{ 297 Properties: properties, 298 } 299 _, err := adminClient.CreateSubscription(ctx, topicName, subscriptionName, &opts) 300 return err 301 } 302 303 // deleteSubscription removes a Service Bus Subscription on a given Namespace and Topic. 304 func deleteSubscription(ctx context.Context, topicName string, subscriptionName string, adminClient *admin.Client) error { 305 se, _ := adminClient.GetSubscription(ctx, topicName, subscriptionName, nil) 306 if se != nil { 307 _, err := adminClient.DeleteSubscription(ctx, topicName, subscriptionName, nil) 308 return err 309 } 310 return nil 311 } 312 313 func BenchmarkAzureServiceBusPubSub(b *testing.B) { 314 const ( 315 benchmarkTopicName = "benchmark-topic" 316 benchmarkSubscriptionName = "benchmark-subscription" 317 ) 318 ctx := context.Background() 319 320 if connString == "" { 321 b.Fatal("azuresb: benchmark requires environment variable SERVICEBUS_CONNECTION_STRING to run") 322 } 323 adminClient, err := admin.NewClientFromConnectionString(connString, nil) 324 if err != nil { 325 b.Fatal(err) 326 } 327 sbClient, err := NewClientFromConnectionString(connString, nil) 328 if err != nil { 329 b.Fatal(err) 330 } 331 332 // Make topic. 333 if err := createTopic(ctx, benchmarkTopicName, adminClient, nil); err != nil { 334 b.Fatal(err) 335 } 336 defer deleteTopic(ctx, benchmarkTopicName, adminClient) 337 338 sbSender, err := NewSender(sbClient, benchmarkTopicName, nil) 339 if err != nil { 340 b.Fatal(err) 341 } 342 defer sbSender.Close(ctx) 343 topic, err := OpenTopic(ctx, sbSender, nil) 344 if err != nil { 345 b.Fatal(err) 346 } 347 defer topic.Shutdown(ctx) 348 349 // Make subscription. 350 if err := createSubscription(ctx, benchmarkTopicName, benchmarkSubscriptionName, adminClient, nil); err != nil { 351 b.Fatal(err) 352 } 353 sbReceiver, err := NewReceiver(sbClient, benchmarkTopicName, benchmarkSubscriptionName, nil) 354 if err != nil { 355 b.Fatal(err) 356 } 357 sub, err := OpenSubscription(ctx, sbClient, sbReceiver, nil) 358 if err != nil { 359 b.Fatal(err) 360 } 361 defer sub.Shutdown(ctx) 362 363 drivertest.RunBenchmarks(b, topic, sub) 364 } 365 366 func fakeConnectionStringInEnv() func() { 367 oldEnvVal := os.Getenv("SERVICEBUS_CONNECTION_STRING") 368 os.Setenv("SERVICEBUS_CONNECTION_STRING", "Endpoint=sb://foo.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=mykey") 369 return func() { 370 os.Setenv("SERVICEBUS_CONNECTION_STRING", oldEnvVal) 371 } 372 } 373 374 func TestOpenTopicFromURL(t *testing.T) { 375 cleanup := fakeConnectionStringInEnv() 376 defer cleanup() 377 378 tests := []struct { 379 URL string 380 WantErr bool 381 }{ 382 // OK. 383 {"azuresb://mytopic", false}, 384 // Invalid parameter. 385 {"azuresb://mytopic?param=value", true}, 386 } 387 388 ctx := context.Background() 389 for _, test := range tests { 390 topic, err := pubsub.OpenTopic(ctx, test.URL) 391 if (err != nil) != test.WantErr { 392 t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr) 393 } 394 if topic != nil { 395 topic.Shutdown(ctx) 396 } 397 } 398 } 399 400 func TestOpenSubscriptionFromURL(t *testing.T) { 401 cleanup := fakeConnectionStringInEnv() 402 defer cleanup() 403 404 tests := []struct { 405 URL string 406 WantErr bool 407 }{ 408 // OK. 409 {"azuresb://mytopic?subscription=mysub", false}, 410 // Missing subscription. 411 {"azuresb://mytopic", true}, 412 // Invalid parameter. 413 {"azuresb://mytopic?subscription=mysub¶m=value", true}, 414 } 415 416 ctx := context.Background() 417 for _, test := range tests { 418 sub, err := pubsub.OpenSubscription(ctx, test.URL) 419 if (err != nil) != test.WantErr { 420 t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr) 421 } 422 if sub != nil { 423 sub.Shutdown(ctx) 424 } 425 } 426 }