get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/test/services_test.go (about) 1 // Copyright 2020 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package test 15 16 import ( 17 "bytes" 18 "fmt" 19 "strings" 20 "testing" 21 "time" 22 23 "get.pme.sh/pnats/server" 24 "github.com/nats-io/nats.go" 25 ) 26 27 var basicMASetupContents = []byte(` 28 server_name: A 29 listen: 127.0.0.1:-1 30 31 accounts: { 32 A: { 33 users: [ {user: a, password: pwd} ] 34 exports: [{service: "foo", response: stream}] 35 }, 36 B: { 37 users: [{user: b, password: pwd} ] 38 imports: [{ service: { account: A, subject: "foo"}, to: "foo_request" }] 39 } 40 } 41 `) 42 43 func TestServiceImportWithStreamed(t *testing.T) { 44 conf := createConfFile(t, basicMASetupContents) 45 46 srv, opts := RunServerWithConfig(conf) 47 defer srv.Shutdown() 48 49 // Limit max response maps here for the test. 50 accB, err := srv.LookupAccount("B") 51 if err != nil { 52 t.Fatalf("Error looking up account: %v", err) 53 } 54 55 // connect and offer a service 56 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 57 if err != nil { 58 t.Fatalf("Error on connect: %v", err) 59 } 60 defer nc.Close() 61 62 nc.Subscribe("foo", func(msg *nats.Msg) { 63 if err := msg.Respond([]byte("world")); err != nil { 64 t.Fatalf("Error on respond: %v", err) 65 } 66 }) 67 nc.Flush() 68 69 nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port)) 70 if err != nil { 71 t.Fatalf("Error on connect: %v", err) 72 } 73 defer nc2.Close() 74 75 numRequests := 10 76 for i := 0; i < numRequests; i++ { 77 resp, err := nc2.Request("foo_request", []byte("hello"), 2*time.Second) 78 if err != nil { 79 t.Fatalf("Unexpected error: %v", err) 80 } 81 if resp == nil || strings.Compare("world", string(resp.Data)) != 0 { 82 t.Fatal("Did not receive the correct message") 83 } 84 } 85 86 // Since we are using a new client that multiplexes, until the client itself goes away 87 // we will have the full number of entries, even with the tighter ResponseEntriesPruneThreshold. 88 accA, err := srv.LookupAccount("A") 89 if err != nil { 90 t.Fatalf("Error looking up account: %v", err) 91 } 92 93 // These should always be the same now. 94 if nre := accB.NumPendingReverseResponses(); nre != numRequests { 95 t.Fatalf("Expected %d entries, got %d", numRequests, nre) 96 } 97 if nre := accA.NumPendingAllResponses(); nre != numRequests { 98 t.Fatalf("Expected %d entries, got %d", numRequests, nre) 99 } 100 101 // Now kill of the client that was doing the requests. 102 nc2.Close() 103 104 checkFor(t, time.Second, 10*time.Millisecond, func() error { 105 aNrssi := accA.NumPendingAllResponses() 106 bNre := accB.NumPendingReverseResponses() 107 if aNrssi != 0 || bNre != 0 { 108 return fmt.Errorf("Response imports and response entries should all be 0, got %d %d", aNrssi, bNre) 109 } 110 return nil 111 }) 112 113 // Now let's test old style request and reply that uses a new inbox each time. This should work ok.. 114 nc2, err = nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port), nats.UseOldRequestStyle()) 115 if err != nil { 116 t.Fatalf("Error on connect: %v", err) 117 } 118 defer nc2.Close() 119 120 for i := 0; i < numRequests; i++ { 121 resp, err := nc2.Request("foo_request", []byte("hello"), 2*time.Second) 122 if err != nil { 123 t.Fatalf("Unexpected error: %v", err) 124 } 125 if resp == nil || strings.Compare("world", string(resp.Data)) != 0 { 126 t.Fatal("Did not receive the correct message") 127 } 128 } 129 130 checkFor(t, time.Second, 10*time.Millisecond, func() error { 131 aNrssi := accA.NumPendingAllResponses() 132 bNre := accB.NumPendingReverseResponses() 133 if aNrssi != 0 || bNre != 0 { 134 return fmt.Errorf("Response imports and response entries should all be 0, got %d %d", aNrssi, bNre) 135 } 136 return nil 137 }) 138 } 139 140 func TestServiceImportWithStreamedResponseAndEOF(t *testing.T) { 141 conf := createConfFile(t, basicMASetupContents) 142 143 srv, opts := RunServerWithConfig(conf) 144 defer srv.Shutdown() 145 146 accA, err := srv.LookupAccount("A") 147 if err != nil { 148 t.Fatalf("Error looking up account: %v", err) 149 } 150 accB, err := srv.LookupAccount("B") 151 if err != nil { 152 t.Fatalf("Error looking up account: %v", err) 153 } 154 155 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 156 if err != nil { 157 t.Fatalf("Error on connect: %v", err) 158 } 159 defer nc.Close() 160 161 // We will send four responses and then and nil message signaling EOF 162 nc.Subscribe("foo", func(msg *nats.Msg) { 163 // Streamed response. 164 msg.Respond([]byte("world-1")) 165 msg.Respond([]byte("world-2")) 166 msg.Respond([]byte("world-3")) 167 msg.Respond([]byte("world-4")) 168 msg.Respond(nil) 169 }) 170 nc.Flush() 171 172 // Now setup requester. 173 nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port)) 174 if err != nil { 175 t.Fatalf("Error on connect: %v", err) 176 } 177 defer nc2.Close() 178 179 numRequests := 10 180 expectedResponses := 5 181 182 for i := 0; i < numRequests; i++ { 183 // Create an inbox 184 reply := nats.NewInbox() 185 sub, _ := nc2.SubscribeSync(reply) 186 defer sub.Unsubscribe() 187 188 if err := nc2.PublishRequest("foo_request", reply, []byte("XOXO")); err != nil { 189 t.Fatalf("Error sending request: %v", err) 190 } 191 192 // Wait and make sure we get all the responses. Should be five. 193 checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error { 194 if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != expectedResponses { 195 return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, expectedResponses) 196 } 197 return nil 198 }) 199 } 200 201 if nre := accA.NumPendingAllResponses(); nre != 0 { 202 t.Fatalf("Expected no entries, got %d", nre) 203 } 204 if nre := accB.NumPendingReverseResponses(); nre != 0 { 205 t.Fatalf("Expected no entries, got %d", nre) 206 } 207 } 208 209 func TestServiceExportsResponseFiltering(t *testing.T) { 210 conf := createConfFile(t, []byte(` 211 server_name: A 212 listen: 127.0.0.1:-1 213 214 accounts: { 215 A: { 216 users: [ {user: a, password: pwd} ] 217 exports: [ {service: "foo"}, {service: "bar"} ] 218 }, 219 B: { 220 users: [{user: b, password: pwd} ] 221 imports: [ {service: { account: A, subject: "foo"}}, {service: { account: A, subject: "bar"}, to: "baz"} ] 222 } 223 } 224 `)) 225 226 srv, opts := RunServerWithConfig(conf) 227 defer srv.Shutdown() 228 229 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 230 if err != nil { 231 t.Fatalf("Error on connect: %v", err) 232 } 233 defer nc.Close() 234 235 // If we do not subscribe the system is now smart enough to not setup the response service imports. 236 nc.SubscribeSync("foo") 237 nc.SubscribeSync("bar") 238 nc.Flush() 239 240 nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port)) 241 if err != nil { 242 t.Fatalf("Error on connect: %v", err) 243 } 244 defer nc2.Close() 245 246 // We don't expect responses, so just do publishes. 247 // 5 for foo 248 sendFoo := 5 249 for i := 0; i < sendFoo; i++ { 250 nc2.PublishRequest("foo", "reply", nil) 251 } 252 // 17 for bar 253 sendBar := 17 254 for i := 0; i < sendBar; i++ { 255 nc2.PublishRequest("baz", "reply", nil) 256 } 257 nc2.Flush() 258 259 accA, err := srv.LookupAccount("A") 260 if err != nil { 261 t.Fatalf("Error looking up account: %v", err) 262 } 263 264 sendTotal := sendFoo + sendBar 265 if nre := accA.NumPendingAllResponses(); nre != sendTotal { 266 t.Fatalf("Expected %d entries, got %d", sendTotal, nre) 267 } 268 269 if nre := accA.NumPendingResponses("foo"); nre != sendFoo { 270 t.Fatalf("Expected %d entries, got %d", sendFoo, nre) 271 } 272 273 if nre := accA.NumPendingResponses("bar"); nre != sendBar { 274 t.Fatalf("Expected %d entries, got %d", sendBar, nre) 275 } 276 } 277 278 func TestServiceExportsAutoDirectCleanup(t *testing.T) { 279 conf := createConfFile(t, []byte(` 280 listen: 127.0.0.1:-1 281 accounts: { 282 A: { 283 users: [ {user: a, password: pwd} ] 284 exports: [ {service: "foo"} ] 285 }, 286 B: { 287 users: [{user: b, password: pwd} ] 288 imports: [ {service: { account: A, subject: "foo"}} ] 289 } 290 } 291 `)) 292 293 srv, opts := RunServerWithConfig(conf) 294 defer srv.Shutdown() 295 296 acc, err := srv.LookupAccount("A") 297 if err != nil { 298 t.Fatalf("Error looking up account: %v", err) 299 } 300 301 // Potential resonder. 302 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 303 if err != nil { 304 t.Fatalf("Error on connect: %v", err) 305 } 306 defer nc.Close() 307 308 // Requestor 309 nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port)) 310 if err != nil { 311 t.Fatalf("Error on connect: %v", err) 312 } 313 defer nc2.Close() 314 315 expectNone := func() { 316 t.Helper() 317 if nre := acc.NumPendingAllResponses(); nre != 0 { 318 t.Fatalf("Expected no entries, got %d", nre) 319 } 320 } 321 322 toSend := 10 323 324 // With no responders we should never register service import responses etc. 325 for i := 0; i < toSend; i++ { 326 nc2.PublishRequest("foo", "reply", nil) 327 } 328 nc2.Flush() 329 expectNone() 330 331 // Now register a responder. 332 sub, _ := nc.Subscribe("foo", func(msg *nats.Msg) { 333 msg.Respond([]byte("world")) 334 }) 335 nc.Flush() 336 defer sub.Unsubscribe() 337 338 // With no reply subject on a request we should never register service import responses etc. 339 for i := 0; i < toSend; i++ { 340 nc2.Publish("foo", nil) 341 } 342 nc2.Flush() 343 expectNone() 344 345 // Create an old request style client. 346 nc3, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port), nats.UseOldRequestStyle()) 347 if err != nil { 348 t.Fatalf("Error on connect: %v", err) 349 } 350 defer nc3.Close() 351 352 // If the request loses interest before the response we should not queue up service import responses either. 353 // This only works for old style requests at the moment where we can detect interest going away. 354 delay := 25 * time.Millisecond 355 sub.Unsubscribe() 356 sub, _ = nc.Subscribe("foo", func(msg *nats.Msg) { 357 time.Sleep(delay) 358 msg.Respond([]byte("world")) 359 }) 360 nc.Flush() 361 defer sub.Unsubscribe() 362 363 for i := 0; i < toSend; i++ { 364 nc3.Request("foo", nil, time.Millisecond) 365 } 366 nc3.Flush() 367 time.Sleep(time.Duration(toSend) * delay * 2) 368 expectNone() 369 } 370 371 // In some instances we do not have a forceful trigger that signals us to clean up. 372 // Like a stream that does not send EOF or a responder who receives requests but does 373 // not answer. For these we will have an expectation of a response threshold which 374 // tells the system we should have seen a response by T, say 2 minutes, 30 seconds etc. 375 func TestServiceExportsPruningCleanup(t *testing.T) { 376 conf := createConfFile(t, []byte(` 377 listen: 127.0.0.1:-1 378 accounts: { 379 A: { 380 users: [ {user: a, password: pwd} ] 381 exports: [ {service: "foo", response: stream} ] 382 }, 383 B: { 384 users: [{user: b, password: pwd} ] 385 imports: [ {service: { account: A, subject: "foo"}} ] 386 } 387 } 388 `)) 389 390 srv, opts := RunServerWithConfig(conf) 391 defer srv.Shutdown() 392 393 // Potential resonder. 394 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 395 if err != nil { 396 t.Fatalf("Error on connect: %v", err) 397 } 398 defer nc.Close() 399 400 // We will subscribe but not answer. 401 sub, _ := nc.Subscribe("foo", func(msg *nats.Msg) {}) 402 nc.Flush() 403 defer sub.Unsubscribe() 404 405 acc, err := srv.LookupAccount("A") 406 if err != nil { 407 t.Fatalf("Error looking up account: %v", err) 408 } 409 410 // Check on response thresholds. 411 rt, err := acc.ServiceExportResponseThreshold("foo") 412 if err != nil { 413 t.Fatalf("Error retrieving response threshold, %v", err) 414 } 415 416 if rt != server.DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD { 417 t.Fatalf("Expected the response threshold to be %v, got %v", 418 server.DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD, rt) 419 } 420 // now set it 421 newRt := 10 * time.Millisecond 422 if err := acc.SetServiceExportResponseThreshold("foo", newRt); err != nil { 423 t.Fatalf("Expected no error setting response threshold, got %v", err) 424 } 425 426 expectedPending := func(expected int) { 427 t.Helper() 428 // Caller is sleeping a bit before, but avoid flappers. 429 checkFor(t, time.Second, 15*time.Millisecond, func() error { 430 if nre := acc.NumPendingResponses("foo"); nre != expected { 431 return fmt.Errorf("Expected %d entries, got %d", expected, nre) 432 } 433 return nil 434 }) 435 } 436 437 // Requestor 438 nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port)) 439 if err != nil { 440 t.Fatalf("Error on connect: %v", err) 441 } 442 defer nc2.Close() 443 444 toSend := 10 445 446 // This should register and they will dangle. Make sure we clean them up. 447 for i := 0; i < toSend; i++ { 448 nc2.PublishRequest("foo", "reply", nil) 449 } 450 nc2.Flush() 451 452 expectedPending(10) 453 time.Sleep(4 * newRt) 454 expectedPending(0) 455 456 // Do it again. 457 for i := 0; i < toSend; i++ { 458 nc2.PublishRequest("foo", "reply", nil) 459 } 460 nc2.Flush() 461 462 expectedPending(10) 463 time.Sleep(4 * newRt) 464 expectedPending(0) 465 } 466 467 func TestServiceExportsResponseThreshold(t *testing.T) { 468 conf := createConfFile(t, []byte(` 469 listen: 127.0.0.1:-1 470 accounts: { 471 A: { 472 users: [ {user: a, password: pwd} ] 473 exports: [ {service: "foo", response: stream, threshold: "1s"} ] 474 }, 475 } 476 `)) 477 478 srv, opts := RunServerWithConfig(conf) 479 defer srv.Shutdown() 480 481 // Potential responder. 482 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 483 if err != nil { 484 t.Fatalf("Error on connect: %v", err) 485 } 486 defer nc.Close() 487 488 // We will subscribe but not answer. 489 sub, _ := nc.Subscribe("foo", func(msg *nats.Msg) {}) 490 nc.Flush() 491 defer sub.Unsubscribe() 492 493 acc, err := srv.LookupAccount("A") 494 if err != nil { 495 t.Fatalf("Error looking up account: %v", err) 496 } 497 498 // Check on response thresholds. 499 rt, err := acc.ServiceExportResponseThreshold("foo") 500 if err != nil { 501 t.Fatalf("Error retrieving response threshold, %v", err) 502 } 503 if rt != 1*time.Second { 504 t.Fatalf("Expected response threshold to be %v, got %v", 1*time.Second, rt) 505 } 506 } 507 508 func TestServiceExportsResponseThresholdChunked(t *testing.T) { 509 conf := createConfFile(t, []byte(` 510 listen: 127.0.0.1:-1 511 accounts: { 512 A: { 513 users: [ {user: a, password: pwd} ] 514 exports: [ {service: "foo", response: chunked, threshold: "10ms"} ] 515 }, 516 B: { 517 users: [{user: b, password: pwd} ] 518 imports: [ {service: { account: A, subject: "foo"}} ] 519 } 520 } 521 `)) 522 523 srv, opts := RunServerWithConfig(conf) 524 defer srv.Shutdown() 525 526 // Responder. 527 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 528 if err != nil { 529 t.Fatalf("Error on connect: %v", err) 530 } 531 defer nc.Close() 532 533 numChunks := 10 534 535 // Respond with 5ms gaps for total response time for all chunks and EOF > 50ms. 536 nc.Subscribe("foo", func(msg *nats.Msg) { 537 // Streamed response. 538 for i := 1; i <= numChunks; i++ { 539 time.Sleep(5 * time.Millisecond) 540 msg.Respond([]byte(fmt.Sprintf("chunk-%d", i))) 541 } 542 msg.Respond(nil) 543 }) 544 nc.Flush() 545 546 // Now setup requester. 547 nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port)) 548 if err != nil { 549 t.Fatalf("Error on connect: %v", err) 550 } 551 defer nc2.Close() 552 553 // Create an inbox 554 reply := nats.NewInbox() 555 sub, _ := nc2.SubscribeSync(reply) 556 defer sub.Unsubscribe() 557 558 if err := nc2.PublishRequest("foo", reply, nil); err != nil { 559 t.Fatalf("Error sending request: %v", err) 560 } 561 562 checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error { 563 if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != numChunks+1 { 564 return fmt.Errorf("Did not receive correct number of chunks: %d vs %d", nmsgs, numChunks+1) 565 } 566 return nil 567 }) 568 } 569 570 func TestServiceAllowResponsesPerms(t *testing.T) { 571 conf := createConfFile(t, []byte(` 572 listen: 127.0.0.1:-1 573 accounts: { 574 A: { 575 users: [ {user: a, password: pwd, permissions = {subscribe=foo, allow_responses=true}} ] 576 exports: [ {service: "foo"} ] 577 }, 578 B: { 579 users: [{user: b, password: pwd} ] 580 imports: [ {service: { account: A, subject: "foo"}} ] 581 } 582 } 583 `)) 584 585 srv, opts := RunServerWithConfig(conf) 586 defer srv.Shutdown() 587 588 // Responder. 589 nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port)) 590 if err != nil { 591 t.Fatalf("Error on connect: %v", err) 592 } 593 defer nc.Close() 594 595 reply := []byte("Hello") 596 // Respond with 5ms gaps for total response time for all chunks and EOF > 50ms. 597 nc.Subscribe("foo", func(msg *nats.Msg) { 598 msg.Respond(reply) 599 }) 600 nc.Flush() 601 602 // Now setup requester. 603 nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port)) 604 if err != nil { 605 t.Fatalf("Error on connect: %v", err) 606 } 607 defer nc2.Close() 608 609 resp, err := nc2.Request("foo", []byte("help"), time.Second) 610 if err != nil { 611 t.Fatalf("Error expecting response %v", err) 612 } 613 if !bytes.Equal(resp.Data, reply) { 614 t.Fatalf("Did not get correct response, %q vs %q", resp.Data, reply) 615 } 616 }