├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── iron_mq.gemspec ├── lib ├── iron_mq.rb └── iron_mq │ ├── client.rb │ ├── messages.rb │ ├── queues.rb │ ├── response.rb │ ├── subscribers.rb │ └── version.rb └── test ├── Rakefile ├── long_run.rb ├── long_run_worker.rb ├── quick_run.rb ├── quick_run2.rb ├── schedule_abt.rb ├── test_base.rb ├── test_bulk.rb ├── test_iron_mq.rb ├── test_mq_worker_subscribers.rb ├── test_performance.rb ├── test_push_queues.rb └── tmp.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | pkg/ 3 | doc 4 | .idea 5 | log 6 | test/config.yml 7 | *.sublime* 8 | /*.gem 9 | config.yml 10 | iron.json 11 | 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | iron_mq (6.0.5) 5 | iron_core (>= 1.0.12) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | beanstalk-client (1.1.1) 11 | concur (2.1.1) 12 | go (1.1.0) 13 | concur 14 | iron_core (1.0.12) 15 | rest (>= 3.0.8) 16 | iron_worker (3.4.2) 17 | iron_core (>= 1.0.12, < 2) 18 | json (~> 2.0) 19 | rest (~> 3.0, >= 3.0.8) 20 | json (2.6.2) 21 | minitest (5.11.3) 22 | net-http-persistent (2.9.4) 23 | netrc (0.11.0) 24 | parallel (1.12.1) 25 | power_assert (1.1.1) 26 | quicky (0.4.0) 27 | rake (12.3.1) 28 | rest (3.0.8) 29 | net-http-persistent (>= 2.9.1, < 3) 30 | netrc 31 | test-unit (3.2.7) 32 | power_assert 33 | uber_config (1.1.3) 34 | 35 | PLATFORMS 36 | ruby 37 | 38 | DEPENDENCIES 39 | beanstalk-client 40 | go 41 | iron_mq! 42 | iron_worker 43 | minitest (>= 5.0) 44 | parallel 45 | quicky 46 | rake 47 | test-unit 48 | uber_config 49 | 50 | BUNDLED WITH 51 | 1.17.3 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Iron.io, Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 15 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 18 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 19 | OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 21 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IronMQ Ruby Client 2 | ------------- 3 | 4 | The [full API documentation is here](http://dev.iron.io/mq/3/reference/api/) and this client tries to stick to the API as 5 | much as possible so if you see an option in the API docs, you can use it in the methods below. 6 | 7 | The versioning of this gem can be confusing when mapping it onto our API 8 | versions. For the v3 version of the MQ API you will want to use a version >= 6.x.x 9 | (this should be you). If you are still using version v2 of the MQ API 10 | (deprecated), you will need to stay at version <= 5.x. 11 | 12 | ## Getting Started 13 | 14 | 1\. Install the gem: 15 | 16 | ```ruby 17 | gem install iron_mq 18 | ``` 19 | 20 | 2\. [Setup your Iron.io credentials](http://dev.iron.io/mq/reference/configuration/) 21 | 22 | 3\. Create an IronMQ client object: 23 | 24 | ```ruby 25 | ironmq = IronMQ::Client.new 26 | ``` 27 | 28 | Or pass in credentials if you don't want to use an iron.json file or set ENV variables: 29 | 30 | ```ruby 31 | ironmq = IronMQ::Client.new(token: 'MY_TOKEN', 32 | project_id: 'MY_PROJECT_ID') 33 | ``` 34 | You can also change the host if you want to use a different cloud or region, for example, to use Rackspace ORD: 35 | 36 | ```ruby 37 | ironmq = IronMQ::Client.new(host: 'mq-rackspace-ord.iron.io', 38 | token:'MY_TOKEN', 39 | project_id: 'MY_PROJECT_ID') 40 | ``` 41 | The default host is AWS us-east-1 zone (mq-aws-us-east-1.iron.io). 42 | 43 | ### Keystone Authentication 44 | 45 | #### Via Configuration File 46 | 47 | Add `keystone` section to your iron.json file: 48 | 49 | ```javascript 50 | { 51 | "project_id": "57a7b7b35e8e331d45000001", 52 | "keystone": { 53 | "server": "http://your.keystone.host/v2.0/", 54 | "tenant": "some-group", 55 | "username": "name", 56 | "password": "password" 57 | } 58 | } 59 | ``` 60 | 61 | #### In Code 62 | 63 | ```ruby 64 | keystone = { 65 | server: "http://your.keystone.host/v2.0/", 66 | tenant: "some-gorup", 67 | username: "name", 68 | password: "password" 69 | } 70 | client = IronMQ::Client.new(project_id: "57a7b7b35e8e331d45000001", keystone: keystone) 71 | ``` 72 | 73 | ## The Basics 74 | 75 | ### Get Queues List 76 | 77 | ```ruby 78 | list_queues = ironmq.queues.list # => [#, ...] 79 | ``` 80 | 81 | -- 82 | 83 | ### Get a Queue Object 84 | 85 | You can have as many queues as you want, each with their own unique set of messages. 86 | 87 | ```ruby 88 | queue = ironmq.queue('my_queue') 89 | ``` 90 | 91 | Now you can use it. 92 | 93 | -- 94 | 95 | ### Post a Message on a Queue 96 | 97 | Messages are placed on the queue in a FIFO arrangement. 98 | If a queue does not exist, it will be created upon the first posting of a message. 99 | 100 | ```ruby 101 | queue.post('hello world!') 102 | ``` 103 | 104 | -- 105 | 106 | ### Retrieve Queue Information 107 | 108 | ```ruby 109 | queue.info 110 | # => {"id"=>"5127bf043264140e863e2283", "name"=>"my_queue", ...} 111 | queue.id 112 | # => "5127bf043264140e863e2283" 113 | ``` 114 | 115 | -- 116 | 117 | ### Reserve/Get a Message from a Queue 118 | 119 | ```ruby 120 | msg = queue.reserve 121 | msg.body 122 | # => "hello world!" 123 | ``` 124 | 125 | When you reserve a message from the queue, it is no longer on the queue but it still exists within the system. 126 | You have to explicitly delete the message or else it will go back onto the queue after the `timeout`. 127 | The default `timeout` is 60 seconds. Minimal `timeout` is 30 seconds. 128 | 129 | -- 130 | 131 | ### Delete a Message from a Queue 132 | 133 | ```ruby 134 | msg.delete 135 | # or 136 | queue.delete(message_id, reservation_id) 137 | ``` 138 | 139 | Be sure to delete a message from the queue when you're done with it. 140 | 141 | ```ruby 142 | messages = queue.reserve(n: 3) 143 | queue.delete_reserved_messages(messages) 144 | ``` 145 | 146 | Delete reserved messages when you're done with it. 147 | 148 | -- 149 | 150 | 151 | ## Client 152 | 153 | `IronMQ::Client` is based on `IronCore::Client` and provides easy access to the queues. 154 | 155 | ```ruby 156 | ironmq = IronMQ::Client.new(token: 'MY_TOKEN', 157 | project_id: 'MY_PROJECT_ID') 158 | ``` 159 | 160 | ### List Queues 161 | 162 | ```ruby 163 | all_queues = ironmq.queues.list 164 | # => [#, ...] 165 | # or 166 | all_queues = ironmq.queues.all 167 | # => [#, ...] 168 | ``` 169 | 170 | **Optional parameters:** 171 | 172 | * `per_page`: number of elements in response, default is 30. 173 | * `previous`: this is the last queue on the previous page, it will start from the next one. If queue with specified 174 | name doesn’t exist result will contain first per_page queues that lexicographically greater than previous 175 | * `prefix`: an optional queue prefix to search on. e.g., prefix=ca could return queues `["cars", "cats", etc.]` 176 | * `raw`: Set it to true to obtain data in raw format. The default is false. 177 | 178 | ```ruby 179 | queues = ironmq.queues.all(per_page: 10, previous: 'test_queue') 180 | ``` 181 | 182 | -- 183 | 184 | ### Get Queue by Name 185 | 186 | ```ruby 187 | queue = ironmq.queue('my_queue') 188 | # => # 189 | ``` 190 | 191 | **Note:** if queue with desired name does not exist it returns fake queue. 192 | Queue will be created automatically on post of first message or queue configuration update. 193 | 194 | -- 195 | 196 | ## Queues 197 | 198 | ### Create a Queue 199 | 200 | ```ruby 201 | ironmq = IronMQ::Client.new 202 | options = { 203 | message_timeout: 120, 204 | message_expiration: 24 * 3600, 205 | push: { 206 | subscribers: [ 207 | { 208 | name: 'subscriber_name', 209 | url: 'http://rest-test.iron.io/code/200?store=key1', 210 | headers: { 211 | 'Content-Type' => 'application/json' 212 | } 213 | } 214 | ], 215 | retries: 3, 216 | retries_delay: 30, 217 | error_queue: 'error_queue_name' 218 | } 219 | } 220 | 221 | ironmq.create_queue(options) 222 | ``` 223 | 224 | **Options:** 225 | 226 | * `type`: String or symbol. Queue type. `:pull`, `:multicast`, `:unicast`. Field required and static. 227 | * `message_timeout`: Integer. Number of seconds before message back to queue if it will not be deleted or touched. 228 | * `message_expiration`: Integer. Number of seconds between message post to queue and before message will be expired. 229 | 230 | **Push queues only:** 231 | 232 | * `push: subscribers`: An array of subscriber hashes containing a `name` and a `url` required fields, 233 | and optional `headers` hash. `headers`'s keys are names and values are means of HTTP headers. 234 | This set of subscribers will replace the existing subscribers. 235 | To add or remove subscribers, see the add subscribers endpoint or the remove subscribers endpoint. 236 | See below for example json. 237 | * `push: retries`: How many times to retry on failure. Default is 3. Maximum is 100. 238 | * `push: retries_delay`: Delay between each retry in seconds. Default is 60. 239 | * `push: error_queue`: String. Queue name to post push errors to. 240 | 241 | -- 242 | 243 | ### Add subscribers to a push queue 244 | 245 | ```ruby 246 | subscribers = [ 247 | { 248 | name: 'first', 249 | url: 'http://first.endpoint.xx/process', 250 | headers: { 251 | Content-Type: 'application/json' 252 | } 253 | }, 254 | { 255 | name: 'second', 256 | url: 'http://second.endpoint.xx/process', 257 | } 258 | ] 259 | queue.add_subscribers(subscribers) 260 | ``` 261 | 262 | ### Replace subscribers on a push queue 263 | 264 | Sets list of subscribers to a queue. Older subscribers will be removed. 265 | 266 | ```ruby 267 | subscribers = [ 268 | { 269 | name: 'the_only', 270 | url: 'http://my.over9k.host.com/push' 271 | } 272 | ] 273 | queue.replace_subscribers(subscribers); 274 | ``` 275 | 276 | ### Remove subscribers by a name from a push queue 277 | 278 | ```ruby 279 | subscribers = [ 280 | { 281 | name: 'the_only' 282 | } 283 | ] 284 | queue.remove_subscribers(subscribers) 285 | ``` 286 | -- 287 | 288 | ### Retrieve Queue Information 289 | 290 | ```ruby 291 | info = queue.info 292 | # => {"id"=>"5127bf043264140e863e2283", "name"=>"my_queue", ...} 293 | ``` 294 | 295 | Shortcuts for `queue.info[key]`: 296 | 297 | ```ruby 298 | id = queue.id # => "5127bf043264140e863e2283" 299 | 300 | size = queue.size # => 7 301 | name = queue.name # => "my_queue" 302 | overall_messages = queue.total_messages # => 13 303 | subscribers = queue.subscribers 304 | # => [{"url" => "http://..."}, ...] 305 | 306 | push_type = queue.push_type # => "multicast" 307 | # Does queue Push Queue? Alias for `queue.push_type.nil?` 308 | is_push_queue = queue.push_queue? # => true 309 | ``` 310 | 311 | **Warning:** to be sure configuration information is up-to-date 312 | client library call IronMQ API each time you request for any parameter except `queue.name`. 313 | In this case you may prefer to use `queue.info` to have `Hash` with all available info parameters. 314 | 315 | -- 316 | 317 | ### Delete a Message Queue 318 | 319 | ```ruby 320 | response = queue.delete # => # 321 | ``` 322 | 323 | -- 324 | 325 | ### Post Messages to a Queue 326 | 327 | **Single message:** 328 | 329 | ```ruby 330 | response = queue.post('something helpful') # => # 331 | # or 332 | response = queue.post('with parameteres', timeout: 300) 333 | # => # 334 | 335 | message_id = response.id # => "5847899158098068288" 336 | status_message = response.msg # => "Messages put on queue." 337 | http_code = response.code # => 200 338 | ``` 339 | 340 | **Multiple messages:** 341 | ```ruby 342 | # [{body: VALUE}, ...] format is required 343 | messages = [{body: 'first'}, {body: 'second'}] 344 | 345 | response = queue.post(messages) 346 | # => {"ids" => ["5847899158098068288", ...], "msg" => "Messages put on queue."} 347 | # or 348 | response = queue.post(messages, timeout: 300) 349 | # => {"ids" => ["5847899158098068288", ...], "msg" => "Messages put on queue."} 350 | ``` 351 | 352 | **Optional parameters:** 353 | 354 | * `delay`: The item will not be available on the queue until this many seconds have passed. 355 | Default is 0 seconds. Maximum is 604,800 seconds (7 days). 356 | 357 | -- 358 | 359 | ### Get Messages from a Queue 360 | 361 | ```ruby 362 | message = queue.reserve # => # 363 | 364 | # or N messages 365 | messages = queue.reserve(n: 7) # => [#, ...] 366 | 367 | # or message by ID 368 | message = queue.get_message '5127bf043264140e863e2283' 369 | # => # 370 | ``` 371 | 372 | **Optional parameters:** 373 | 374 | * `n`: The maximum number of messages to get. Default is 1. Maximum is 100. 375 | * `timeout`: After timeout (in seconds), item will be placed back onto queue. 376 | You must delete the message from the queue to ensure it does not go back onto the queue. 377 | If not set, value from POST is used. Default is 60 seconds. Minimum is 30 seconds. 378 | Maximum is 86,400 seconds (24 hours). 379 | * `wait`: : Time in seconds to wait for a message to become available. This enables long polling. Default is 0 (does not wait), maximum is 30. 380 | * `delete`: true/false. This will delete the message on get. Be careful though, only use this if you are ok with losing a message if something goes wrong after you get it. Default is false. 381 | 382 | When `n` parameter is specified and greater than 1 method returns `Array` of `Message`s. 383 | Otherwise, `Message` object would be returned. 384 | 385 | -- 386 | 387 | ### Touch a Message on a Queue 388 | 389 | Touching a reserved message extends its timeout by the duration specified when the message was created, which is 60 seconds by default. 390 | 391 | ```ruby 392 | message = queue.reserve # => # 393 | 394 | message.touch # => # 395 | ``` 396 | 397 | -- 398 | 399 | ### Release Message 400 | 401 | ```ruby 402 | message = queue.reserve # => # 403 | 404 | response = message.release # => # 405 | # or 406 | response = message.release(delay: 42) # => # 407 | ``` 408 | 409 | **Optional parameters:** 410 | 411 | * `delay`: The item will not be available on the queue until this many seconds have passed. 412 | Default is 0 seconds. Maximum is 604,800 seconds (7 days). 413 | 414 | -- 415 | 416 | ### Delete a Message from a Queue 417 | 418 | ```ruby 419 | message = queue.reserve # => # 420 | 421 | message.delete # => # 422 | ``` 423 | 424 | -- 425 | 426 | ### Peek Messages from a Queue 427 | 428 | Peeking at a queue returns the next messages on the queue, but it does not reserve them. 429 | 430 | ```ruby 431 | message = queue.peek # => # 432 | # or multiple messages 433 | messages = queue.peek(n: 13) 434 | # => [#, ...] 435 | ``` 436 | 437 | **Optional parameters:** 438 | 439 | * `n`: The maximum number of messages to peek. Default is 1. Maximum is 100. 440 | 441 | -- 442 | 443 | ### Poll for Messages 444 | 445 | ```ruby 446 | queue.poll { |msg| puts msg.body } 447 | ``` 448 | 449 | Polling will automatically delete the message at the end of the block. 450 | 451 | -- 452 | 453 | ### Clear a Queue 454 | 455 | ```ruby 456 | queue.clear # => # 457 | ``` 458 | 459 | -- 460 | 461 | 462 | ## Push Queues 463 | 464 | IronMQ push queues allow you to setup a queue that will push to an endpoint, rather than having to poll the endpoint. 465 | [Here's the announcement for an overview](http://blog.iron.io/2013/01/ironmq-push-queues-reliable-message.html). 466 | 467 | ### Update a Message Queue 468 | 469 | ```ruby 470 | options = { 471 | message_timeout: 120, 472 | message_expiration: 24 * 3600, 473 | push: { 474 | subscribers: [ 475 | { 476 | name: 'subscriber_name', 477 | url: 'http://rest-test.iron.io/code/200?store=key1', 478 | headers: { 479 | 'Content-Type' => 'application/json' 480 | } 481 | } 482 | ], 483 | retries: 3, 484 | retries_delay: 30, 485 | error_queue: 'error_queue_name' 486 | } 487 | } 488 | 489 | queue.update(options) 490 | ``` 491 | 492 | **The following parameters are all related to Push Queues:** 493 | 494 | * `push: subscribers`: An array of subscriber hashes containing a `name` and a `url` required fields, 495 | and optional `headers` hash. `headers`'s keys are names and values are means of HTTP headers. 496 | This set of subscribers will replace the existing subscribers. 497 | To add or remove subscribers, see the add subscribers endpoint or the remove subscribers endpoint. 498 | See below for example json. 499 | * `push: retries`: How many times to retry on failure. Default is 3. Maximum is 100. 500 | * `push: retries_delay`: Delay between each retry in seconds. Default is 60. 501 | * `push: error_queue`: String. Queue name to post push errors to. 502 | 503 | **Note:** queue type cannot be changed. 504 | 505 | -- 506 | 507 | ### Set Subscribers on a Queue 508 | 509 | Subscribers can be any HTTP endpoint. push `type` is one of: 510 | 511 | * `multicast`: will push to all endpoints/subscribers 512 | * `unicast`: will push to one and only one endpoint/subscriber 513 | 514 | ```ruby 515 | ironmq = IronMQ::Client.new 516 | subscribers = 517 | [ 518 | { 519 | name: 'key-one-sub', 520 | url: 'http://rest-test.iron.io/code/200?store=key1' 521 | }, 522 | { 523 | name: 'key-two-sub', 524 | url: 'http://rest-test.iron.io/code/200?store=key2' 525 | } 526 | ] 527 | ironmq.create_queue('queue_name', type: :multicast, 528 | push: {subscribers: subscribers}) 529 | ``` 530 | 531 | -- 532 | 533 | ### Add/Remove Subscribers on a Queue 534 | 535 | ```ruby 536 | queue.add_subscriber({ 537 | name: 'nowhere', 538 | url: 'http://nowhere.com/push' 539 | }) 540 | 541 | queue.add_subscribers([ 542 | { 543 | name: 'first', 544 | url: 'http://first.endpoint.xx/process', 545 | headers: { 546 | 'Content-Type': 'application/json' 547 | } 548 | }, 549 | { 550 | name: 'second', 551 | url: 'http://second.endpoint.xx/process' 552 | } 553 | ]) 554 | 555 | queue.clear_subscribers 556 | ``` 557 | 558 | -- 559 | 560 | ### Post and instantiate 561 | 562 | Sometimes you may want to post message to the Push Queue and instantiate `Message` 563 | instead getting it by ID returned in API response. To do this just set `:instantiate` 564 | to `true`. 565 | 566 | ```ruby 567 | message = queue.post('push me!', instantiate: true) 568 | # => # 569 | 570 | msgs = queue.post([{body: 'push'}, {body: 'me'}], instantiate: true) 571 | # => [#, ...] 572 | ``` 573 | 574 | This creates fake `Message` objects. They contain only IDs. 575 | 576 | -- 577 | 578 | ### Get Message Push Status 579 | 580 | After pushing a message: 581 | 582 | ```ruby 583 | statuses = queue.get_message(msg.id).push_statuses 584 | # => [#, ...] 585 | 586 | statuses.each do |s| 587 | puts "#{s.subscriber_name}: #{(s.code == 200) ? 'Success' : 'Fail'}" 588 | end 589 | ``` 590 | 591 | Returns an array of subscribers with status. 592 | 593 | **Note:** getting a message by ID is only for usable for Push Queues. 594 | This creates fake `IronMQ::Message` instance on which you call for subscribers' push statuses. 595 | 596 | -- 597 | 598 | ### Acknowledge / Delete Message Push Status 599 | 600 | ```ruby 601 | subscribers = queue.get_message(msg.id).subscribers 602 | # => [#, ...] 603 | 604 | subscribers.each do |ss| 605 | ss.delete 606 | # ss.acknowledge # This is `delete`'s alias 607 | end 608 | ``` 609 | 610 | -- 611 | 612 | 613 | ## Important Notes 614 | 615 | * [Ruby 1.8 is no more supported](https://www.ruby-lang.org/en/news/2013/06/30/we-retire-1-8-7/). 616 | * Queue type is static now. Once it is set, it cannot be changed. 617 | 618 | ## Further Links 619 | 620 | * [IronMQ Overview](http://dev.iron.io/mq/) 621 | * [IronMQ REST/HTTP API](http://dev.iron.io/mq/reference/api/) 622 | * [Push Queues](http://dev.iron.io/mq/reference/push_queues/) 623 | * [Other Client Libraries](http://dev.iron.io/mq/libraries/) 624 | * [Live Chat, Support & Fun](http://get.iron.io/chat) 625 | 626 | ------------- 627 | © 2011 - 2013 Iron.io Inc. All Rights Reserved. 628 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | require 'rake/testtask' 5 | Rake::TestTask.new(:test) do |test| 6 | test.libs << 'lib' << 'test' 7 | test.pattern = 'test/**/test_*.rb' 8 | test.verbose = true 9 | end 10 | 11 | task :default => :test 12 | 13 | require 'rdoc/task' 14 | Rake::RDocTask.new do |rdoc| 15 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 16 | 17 | rdoc.rdoc_dir = 'doc' 18 | rdoc.title = "iron_mq #{version}" 19 | rdoc.rdoc_files.include('README*') 20 | rdoc.rdoc_files.include('lib/**/*.rb') 21 | end 22 | -------------------------------------------------------------------------------- /iron_mq.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/iron_mq/version', __FILE__) 2 | 3 | Gem::Specification.new do |gem| 4 | gem.authors = ["Yury Yantsevich", "Travis Reeder"] 5 | gem.email = ["yury@iron.io", "travis@iron.io"] 6 | gem.description = "Ruby client for IronMQ by www.iron.io" 7 | gem.summary = "Ruby client for IronMQ by www.iron.io" 8 | gem.homepage = "https://github.com/iron-io/iron_mq_ruby" 9 | gem.license = "BSD-2-Clause" 10 | 11 | gem.files = `git ls-files`.split($\) 12 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "iron_mq" 15 | gem.require_paths = ["lib"] 16 | gem.version = IronMQ::VERSION 17 | 18 | gem.required_rubygems_version = ">= 1.3.6" 19 | gem.required_ruby_version = Gem::Requirement.new(">= 1.8") 20 | gem.add_runtime_dependency "iron_core", ">= 1.0.12" 21 | 22 | gem.add_development_dependency "test-unit" 23 | gem.add_development_dependency "minitest", ">= 5.0" 24 | gem.add_development_dependency "rake" 25 | gem.add_development_dependency "beanstalk-client" 26 | gem.add_development_dependency "uber_config" 27 | gem.add_development_dependency "quicky" 28 | gem.add_development_dependency "iron_worker" 29 | gem.add_development_dependency "go" 30 | gem.add_development_dependency "parallel" 31 | end 32 | 33 | -------------------------------------------------------------------------------- /lib/iron_mq.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('iron_mq/response', File.dirname(__FILE__)) 2 | require File.expand_path('iron_mq/subscribers', File.dirname(__FILE__)) 3 | require File.expand_path('iron_mq/queues', File.dirname(__FILE__)) 4 | require File.expand_path('iron_mq/messages', File.dirname(__FILE__)) 5 | require File.expand_path('iron_mq/client', File.dirname(__FILE__)) 6 | require File.expand_path('iron_mq/version', File.dirname(__FILE__)) 7 | -------------------------------------------------------------------------------- /lib/iron_mq/client.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | require 'iron_core' 4 | 5 | module IronMQ 6 | 7 | class Client < IronCore::Client 8 | AWS_US_EAST_HOST = 'mq-aws-us-east-1-1.iron.io' 9 | 10 | attr_accessor :queue_name, :logger 11 | 12 | def initialize(options={}) 13 | default_options = { 14 | scheme: 'https', 15 | host: IronMQ::Client::AWS_US_EAST_HOST, 16 | port: 443, 17 | api_version: 3, 18 | user_agent: 'iron_mq_ruby-' + IronMQ::VERSION + 19 | ' (iron_core_ruby-' + IronCore.version + ')' 20 | } 21 | 22 | super('iron', 'mq', options, default_options, 23 | [:project_id, :token, :api_version]) 24 | 25 | if @keystone.nil? 26 | if @token.nil? 27 | IronCore::Logger.error 'IronMQ', 'Token is not set', IronCore::Error 28 | end 29 | 30 | check_id(@project_id, 'project_id') 31 | end 32 | 33 | @logger = Logger.new(STDOUT) 34 | @logger.level = Logger::INFO 35 | end 36 | 37 | def headers 38 | super.merge({'Authorization' => "OAuth #{@token_provider.nil? ? @token : @token_provider.token}"}) 39 | end 40 | 41 | def base_url 42 | @base_url ||= "#{super}#{@api_version}/projects/#{@project_id}/queues" 43 | end 44 | 45 | def queues_list(options = {}) 46 | is_raw = [options.delete(:raw), 47 | options.delete('raw')].compact.first 48 | response = parse_response(get('', options)) # GET base_url 49 | # p response 50 | # returns list of evaluated queues 51 | if is_raw 52 | response.map{ |q_info| ResponseBase.new(q_info) } 53 | else 54 | response['queues'].map { |q_info| Queue.new(self, q_info['name']) } 55 | end 56 | end 57 | 58 | alias_method :list, :queues_list 59 | alias_method :all, :queues_list 60 | 61 | def queues_get(name) 62 | IronMQ::Queue.new(self, name) 63 | end 64 | 65 | alias_method :queue, :queues_get 66 | 67 | # Backward compatibility for 68 | # client.queues.get(name: 'my_queue') 69 | # client.queues.get('name' => 'my_queue') 70 | def get(*args) 71 | if args.size == 1 && args[0].is_a?(Hash) 72 | queue_name = (args[0][:name] || args[0]['name']).to_s 73 | queue_name.empty? ? super : queues_get(queue_name) 74 | else 75 | super 76 | end 77 | end 78 | 79 | # Backward compatibility, adds possibility to call 80 | # client.queues.all 81 | # client.queues.list 82 | # client.queues.queue(name) 83 | def queues 84 | self 85 | end 86 | 87 | def create_queue(queue_name, options) 88 | response = self.put("/#{CGI::escape(queue_name).gsub('+', '%20')}", 89 | {queue: options}) 90 | queue_hash = JSON.parse(response.body.to_s) 91 | 92 | ResponseBase.new(queue_hash['queue']) 93 | end 94 | 95 | def update_queue(queue_name, options) 96 | response = self.patch("/#{CGI::escape(queue_name).gsub('+', '%20')}", 97 | {queue: options}) 98 | queue_hash = JSON.parse(response.body.to_s) 99 | 100 | ResponseBase.new(queue_hash['queue']) 101 | end 102 | 103 | end 104 | 105 | end 106 | -------------------------------------------------------------------------------- /lib/iron_mq/messages.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | 3 | module IronMQ 4 | 5 | class Message < ResponseBase 6 | attr_reader :queue 7 | 8 | def initialize(queue, data) 9 | @queue = queue 10 | super(data, 200) 11 | end 12 | 13 | def body 14 | @raw['body'] 15 | end 16 | 17 | def timeout 18 | @raw['timeout'] 19 | end 20 | 21 | def expires_in 22 | @raw['expires_in'] 23 | end 24 | 25 | def delay 26 | @raw['delay'] 27 | end 28 | 29 | def reserved_count 30 | @raw['reserved_count'] 31 | end 32 | 33 | def reservation_id 34 | @raw['reservation_id'] 35 | end 36 | 37 | def push_statuses 38 | @raw['push_statuses'] 39 | end 40 | 41 | def touch 42 | call_api_and_parse_response(:post, '/touch') 43 | end 44 | 45 | def release(options = {}) 46 | call_api_and_parse_response(:post, '/release', options) 47 | end 48 | 49 | # `options` was kept for backward compatibility 50 | def subscribers(options = {}) 51 | response = call_api_and_parse_response(:get, '/subscribers', {}, false) 52 | 53 | response['subscribers'].map { |s| Subscriber.new(s, self, options) } 54 | end 55 | 56 | def delete 57 | call_api_and_parse_response(:delete) 58 | rescue Rest::HttpError => ex 59 | #if ex.code == 404 60 | # Rest.logger.info('Delete got 404, safe to ignore.') 61 | # # return ResponseBase as normal 62 | # ResponseBase.new({'msg' => 'Deleted'}, 404) 63 | #else 64 | raise ex 65 | #end 66 | end 67 | 68 | def call_api_and_parse_response(meth, ext_path = '', 69 | options = {}, instantiate = true) 70 | if self.reservation_id && !self.reservation_id.empty? 71 | options[:reservation_id] = self.reservation_id 72 | end 73 | @queue.call_api_and_parse_response(meth, 74 | "#{path(ext_path)}", 75 | options, instantiate) 76 | end 77 | 78 | private 79 | 80 | def path(ext_path) 81 | "/messages/#{id}#{ext_path}" 82 | end 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /lib/iron_mq/queues.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | 3 | module IronMQ 4 | 5 | class Queue < ResponseBase 6 | attr_reader :name, :raw 7 | 8 | def initialize(client, queue_name) 9 | @client = client 10 | @name = queue_name 11 | end 12 | 13 | def info 14 | load 15 | end 16 | 17 | # this is only run once if it hasn't been called before unless force is true, then it will force reload. 18 | def load 19 | reload if @raw.nil? 20 | 21 | @raw['queue'] 22 | end 23 | 24 | def reload 25 | @raw = call_api_and_parse_response(:get, '', {}, false, true) 26 | self 27 | end 28 | 29 | def id 30 | load['id'] 31 | end 32 | 33 | def size 34 | load['size'].to_i 35 | end 36 | 37 | def total_messages 38 | load['total_messages'].to_i 39 | end 40 | 41 | def type 42 | load['type'] 43 | end 44 | 45 | def push_queue? 46 | ['multicast', 'unicast'].include?(type) 47 | end 48 | 49 | def push_info 50 | load['push'] 51 | end 52 | 53 | def update(options={}) 54 | res = call_api_and_parse_response(:put, '', {queue: options}) 55 | 56 | oldinfo = @raw ? @raw['queue'] : {} 57 | @raw = res.raw 58 | @raw['queue'].merge!('size' => oldinfo['size'], 'total_messages' => oldinfo['total_messages']) 59 | res 60 | end 61 | 62 | alias_method :update_queue, :update 63 | 64 | def clear 65 | call_api_and_parse_response(:delete, '/messages', {}, false, true) 66 | end 67 | 68 | alias_method :clear_queue, :clear 69 | 70 | # Backward compatibility, better name is `delete` 71 | def delete_queue 72 | r = call_api_and_parse_response(:delete) 73 | @raw = nil 74 | return r 75 | rescue Rest::HttpError => ex 76 | #if ex.code == 404 77 | # Rest.logger.info('Delete got 404, safe to ignore.') 78 | # # return ResponseBase as normal 79 | # ResponseBase.new({'msg' => 'Deleted'}, 404) 80 | #else 81 | raise ex 82 | #end 83 | end 84 | 85 | # Backward compatibility 86 | def delete(message_id, reservation_id = nil) 87 | # API does not accept any options 88 | options = {} 89 | options['id'] = message_id 90 | unless reservation_id.nil? 91 | options['reservation_id'] = reservation_id 92 | end 93 | Message.new(self, options).delete 94 | end 95 | 96 | # Accepts an array of message ids 97 | def delete_messages(ids) 98 | call_api_and_parse_response(:delete, '/messages', ids: ids) 99 | end 100 | 101 | def delete_reserved_messages(messages) 102 | ids = messages.map do |message| 103 | {id: message.id, reservation_id: message.reservation_id} 104 | end 105 | 106 | call_api_and_parse_response(:delete, '/messages', ids: ids) 107 | end 108 | 109 | def add_subscribers(subscribers) 110 | call_api_and_parse_response(:post, '/subscribers',{subscribers: subscribers}) 111 | end 112 | 113 | # `options` for backward compatibility 114 | def add_subscriber(subscriber, options = {}) 115 | add_subscribers([subscriber]) 116 | end 117 | 118 | def remove_subscribers(subscribers) 119 | call_api_and_parse_response(:delete, 120 | '/subscribers', 121 | { 122 | subscribers: subscribers, 123 | headers: { 124 | 'Content-Type' => @client.content_type 125 | } 126 | }) 127 | end 128 | 129 | def remove_subscriber(subscriber) 130 | remove_subscribers([subscriber]) 131 | end 132 | 133 | def replace_subscribers(subscribers) 134 | call_api_and_parse_response(:put, 135 | '/subscribers', 136 | { 137 | subscribers: subscribers, 138 | headers: { 139 | 'Content-Type' => @client.content_type 140 | } 141 | }) 142 | end 143 | 144 | def replace_subscriber(subscriber) 145 | replace_subscribers([subscriber]) 146 | end 147 | 148 | # `options` was kept for backward compatibility 149 | def subscribers(options = {}) 150 | load 151 | return [] if info['push'].nil? || info['push']['subscribers'].nil? 152 | 153 | info['push']['subscribers'].map { |s| Subscriber.new(s, self, options) } 154 | end 155 | 156 | def post_messages(payload, options = {}) 157 | batch = false 158 | 159 | instantiate = [options.delete(:instantiate), 160 | options.delete('instantiate')].compact.first 161 | 162 | msgs = if payload.is_a?(Array) 163 | batch = true 164 | # FIXME: This maybe better to process Array of Objects the same way as for single message. 165 | # 166 | # payload.map { |msg| options.merge(:body => msg) } 167 | # 168 | # For now user must pass objects like `[{:body => msg1}, {:body => msg2}]` 169 | payload.map { |msg| msg.merge(options) } 170 | else 171 | [options.merge(body: payload)] 172 | end 173 | 174 | # Do not instantiate response 175 | res = call_api_and_parse_response(:post, '/messages', 176 | {messages: msgs}, false) 177 | 178 | if instantiate 179 | n = batch ? 2 : 1 180 | msg_ids = res['ids'].map { |id| {'id' => id} } 181 | 182 | process_messages(msg_ids, {n: n}) 183 | else 184 | if batch 185 | # FIXME: Return Array of ResponseBase instead, it seems more clear than raw response 186 | # 187 | # res['ids'].map { |id| ResponseBase.new({'id' => id, 'msg' => res['msg']}) } 188 | # 189 | ResponseBase.new(res) # Backward capable 190 | else 191 | ResponseBase.new({'id' => res['ids'][0], 'msg' => res['msg']}) 192 | end 193 | end 194 | end 195 | 196 | alias_method :post, :post_messages 197 | 198 | def reserve_messages(options = {}) 199 | resp = call_api_and_parse_response(:post, '/reservations', options, false) 200 | process_messages(resp['messages'], options) 201 | end 202 | 203 | # backwards compatibility 204 | alias_method :get, :reserve_messages 205 | alias_method :get_messages, :reserve_messages 206 | alias_method :reserve, :reserve_messages 207 | 208 | # Backward compatibility 209 | def messages 210 | self 211 | end 212 | 213 | def get_message(id) 214 | resp = call_api_and_parse_response(:get, "/messages/#{id}", {}, false) 215 | Message.new(self, resp['message']) 216 | end 217 | 218 | def peek_messages(options = {}) 219 | resp = call_api_and_parse_response(:get, '/messages', options) 220 | 221 | process_messages(resp['messages'], options) 222 | end 223 | 224 | alias_method :peek, :peek_messages 225 | 226 | def poll_messages(options = {}, &block) 227 | sleep_duration = options[:sleep_duration] || 1 228 | 229 | while true 230 | msg = get_messages(options.merge(:n => 1)) 231 | if msg.nil? 232 | options[:break_if_nil] ? break : sleep(sleep_duration) 233 | else 234 | yield msg 235 | # Delete message after processing 236 | msg.delete 237 | end 238 | end 239 | end 240 | 241 | alias_method :poll, :poll_messages 242 | 243 | def call_api_and_parse_response(meth, ext_path = '', options = {}, 244 | instantiate = true, ignore404 = false) 245 | response = 246 | if meth.to_s == 'delete' 247 | headers = options.delete(:headers) || 248 | options.delete('headers') || 249 | Hash.new 250 | headers['Content-Type'] = 'application/json' 251 | @client.parse_response(@client.send(meth, 252 | "#{path(ext_path)}", 253 | options, headers)) 254 | else 255 | @client.parse_response(@client.send(meth, 256 | "#{path(ext_path)}", 257 | options)) 258 | end 259 | 260 | instantiate ? ResponseBase.new(response) : response 261 | end 262 | 263 | private 264 | 265 | def path(ext_path) 266 | "/#{CGI::escape(@name).gsub('+', '%20')}#{ext_path}" 267 | end 268 | 269 | def process_messages(messages, options) 270 | multiple = wait_for_multiple?(options) 271 | 272 | if messages.is_a?(Array) && messages.size > 0 273 | msgs = messages.map { |m| Message.new(self, m) } 274 | 275 | multiple ? msgs : msgs[0] 276 | else 277 | multiple ? [] : nil 278 | end 279 | end 280 | 281 | def wait_for_multiple?(options) 282 | options.is_a?(Hash) && ((options[:n] || options['n']).to_i > 1) 283 | end 284 | end 285 | 286 | end 287 | 288 | -------------------------------------------------------------------------------- /lib/iron_mq/response.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module IronMQ 4 | 5 | 6 | class ResponseBase 7 | attr_reader :raw, :code 8 | 9 | def initialize(data, code = 200) 10 | @raw = data 11 | @code = code 12 | #super(data.merge(:code => code.to_i)) 13 | end 14 | 15 | def id 16 | @raw['id'] 17 | end 18 | 19 | def [](key) 20 | @raw[key] 21 | end 22 | 23 | def msg 24 | @raw['msg'] 25 | end 26 | # 27 | #def raw 28 | # if @raw.nil? 29 | # @raw = call_api_and_parse_response(:get, "", {}, false) 30 | # end 31 | # #res = stringify_keys(marshal_dump) 32 | # ## `code` is not part of response body 33 | # #res.delete("code") 34 | # # 35 | # #res 36 | # @raw 37 | #end 38 | 39 | private 40 | 41 | def stringify_keys(hash) 42 | hash.keys.each { |k| hash[k.to_s] = hash.delete(k) } 43 | 44 | hash 45 | end 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /lib/iron_mq/subscribers.rb: -------------------------------------------------------------------------------- 1 | module IronMQ 2 | 3 | class Subscriber < ResponseBase 4 | # `options` was kept for backward compatibility 5 | attr_accessor :options 6 | 7 | def initialize(data, message, options = {}) 8 | super(data, 200) 9 | @message = message 10 | @options = options 11 | end 12 | 13 | def name 14 | @raw['name'] 15 | end 16 | 17 | def url 18 | @raw['url'] 19 | end 20 | 21 | def headers 22 | @raw['headers'] 23 | end 24 | 25 | # `options` was kept for backward compatibility 26 | def delete(options = {}) 27 | @message.call_api_and_parse_response(:delete, path, 28 | {subscriber_name: name}) 29 | rescue Rest::HttpError => ex 30 | #if ex.code == 404 31 | # Rest.logger.info("Delete got 404, safe to ignore.") 32 | # # return ResponseBase as normal 33 | # ResponseBase.new({"msg" => "Deleted"}, 404) 34 | #else 35 | raise ex 36 | #end 37 | end 38 | 39 | alias_method :acknowledge, :delete 40 | 41 | private 42 | 43 | def path 44 | '' # path is the same as for message delete endpoint 45 | end 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /lib/iron_mq/version.rb: -------------------------------------------------------------------------------- 1 | module IronMQ 2 | VERSION = "6.0.5" 3 | end 4 | -------------------------------------------------------------------------------- /test/Rakefile: -------------------------------------------------------------------------------- 1 | require 'tmpdir' 2 | require 'rake/testtask' 3 | 4 | Dir.chdir(File.dirname(__FILE__) + '/..') 5 | 6 | Rake::TestTask.new do |t| 7 | if ENV['NEW_PROJECT'] 8 | require_relative '../lib/iron_worker.rb' 9 | 10 | client = IronWorkerNG::Client.new 11 | name = 'IWtest ' + Time.now.strftime('%b %-d %T') 12 | resp = client.api.post('projects', name: name) 13 | res = JSON.parse(resp.body) 14 | raise "Failed to create new project: #{res}" unless 15 | res['msg'].start_with? 'Project Created' 16 | 17 | ENV['IRON_PROJECT_ID'] = res['id'] 18 | end 19 | if ENV['IRON_PROJECT_ID'] 20 | t.options = "-- --project-id=#{ENV['IRON_PROJECT_ID']}" 21 | end 22 | 23 | t.libs << "lib" 24 | 25 | files = FileList['test/**/test_*.rb'] 26 | t.test_files = files.keep_if do |f| 27 | f =~ Regexp.new(ENV['TESTP'] || '') and 28 | not ( r = ENV['EXCLP'] and 29 | f =~ Regexp.new(r) ) 30 | end 31 | 32 | t.verbose = true 33 | end 34 | -------------------------------------------------------------------------------- /test/long_run.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'concur' 3 | require 'uber_config' 4 | 5 | unless Hash.instance_methods.include?(:default_proc=) 6 | class Hash 7 | def default_proc=(proc) 8 | end 9 | end 10 | end 11 | 12 | begin 13 | require File.join(File.dirname(__FILE__), '..', 'lib', 'iron_mq') 14 | rescue Exception => ex 15 | puts "Could NOT load current iron_mq: " + ex.message 16 | raise ex 17 | end 18 | 19 | require_relative 'long_run_worker' 20 | 21 | @config = UberConfig.load 22 | @num_to_add = 10000 23 | 24 | p @config 25 | 26 | worker = LongRunWorker.new 27 | worker.config = @config 28 | worker.queue_name = "concur8" 29 | worker.num_threads = 100 30 | worker.num_to_add = @num_to_add 31 | worker.skip_get_and_delete = false 32 | worker.run 33 | #worker.queue 34 | #status = worker.wait_until_complete 35 | #p status 36 | #puts worker.get_log 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/long_run_worker.rb: -------------------------------------------------------------------------------- 1 | require 'concur' 2 | require 'iron_mq' 3 | 4 | class LongRunWorker 5 | 6 | attr_accessor :config, :num_to_add, :queue_name, :skip_get_and_delete, :num_threads 7 | 8 | def run 9 | 10 | @client = IronMQ::Client.new(@config['iron']) 11 | queue = @client.queue(queue_name) 12 | 13 | @error_count = 0 14 | 15 | start = Time.now 16 | puts "Queuing #{@num_to_add} items at #{start}..." 17 | executor = Concur::Executor.new_thread_pool_executor(num_threads || 20) 18 | @num_to_add.times do |i| 19 | task = executor.execute do 20 | begin 21 | puts "POST #{i}..." 22 | res = queue.post("hello world! #{i}") 23 | rescue => ex 24 | puts "ERROR! #{ex.class.name}: #{ex.message} -- #{ex.backtrace.inspect}" 25 | @error_count += 1 26 | raise ex 27 | end 28 | end 29 | end 30 | 31 | i = 0 32 | while executor.queue_size > 0 do 33 | i += 1 34 | puts "waiting #{i}, queue size=#{executor.queue_size}" 35 | sleep 2 36 | end 37 | put_time = (Time.now.to_f - start.to_f) 38 | sleep 1 39 | puts "Finished pushing in #{put_time} seconds" 40 | 41 | queue = @client.queue(queue_name) 42 | queue_size_after_push = queue.size 43 | puts "QUEUE SIZE: #{queue_size_after_push}" 44 | 45 | #exit if true 46 | 47 | if skip_get_and_delete 48 | start = Time.now 49 | puts "Getting and deleting #{@num_to_add} items at #{start}..." 50 | @num_to_add.times do |i| 51 | task = executor.execute do 52 | puts "GET #{i}..." 53 | res = queue.get() 54 | p res 55 | puts "DELETE #{i}..." 56 | res = queue.delete(res.id) 57 | p res 58 | end 59 | end 60 | i = 0 61 | while executor.queue_size > 0 do 62 | i += 1 63 | puts "waiting #{i}, queue size=#{executor.queue_size}" 64 | sleep 2 65 | end 66 | sleep 1 67 | end 68 | 69 | queue = @client.queue(queue_name) 70 | 71 | puts "Finished pushing #{@num_to_add} items in #{put_time} seconds." 72 | puts "QUEUE SIZE after push: #{queue_size_after_push}" 73 | puts "Finished getting and deleting #{@num_to_add} items in #{(Time.now.to_f - start.to_f)} seconds." 74 | puts "QUEUE SIZE after delete: #{queue.size}" 75 | puts "Errors: #{@error_count}" 76 | 77 | executor.shutdown 78 | 79 | end 80 | 81 | end -------------------------------------------------------------------------------- /test/quick_run.rb: -------------------------------------------------------------------------------- 1 | require 'quicky' 2 | require 'go' 3 | require File.expand_path('test_base.rb', File.dirname(__FILE__)) 4 | 5 | TIMES_PER_THREAD = 10 6 | 7 | class QuickRun < TestBase 8 | 9 | def setup 10 | super 11 | end 12 | 13 | def test_quick 14 | 15 | queue_name = "ironmq_gem_quick_#{rand(100)}" 16 | clear_queue(queue_name) 17 | queue = @client.queue(queue_name) 18 | 19 | quicky = Quicky::Timer.new 20 | j = 0 21 | quicky.loop(:test_quick, TIMES_PER_THREAD) do |i| 22 | puts "==== LOOP #{i || j} ==================================" 23 | j += 1 24 | body = "hello world!" 25 | post_id = nil 26 | quicky.time(:post) do 27 | puts "post" 28 | res = queue.post(body) 29 | # p res 30 | refute_nil res 31 | refute_nil res.id 32 | post_id = res.id 33 | assert !(res.msg.nil? || res.msg.empty?) 34 | end 35 | 36 | quicky.time(:get) do 37 | puts "get" 38 | msg = queue.get 39 | refute_nil msg.id 40 | assert_equal msg.id, post_id 41 | assert !(msg.body.nil? || msg.body.empty?) 42 | assert_equal body, msg.body 43 | end 44 | 45 | quicky.time(:delete) do 46 | puts "delete" 47 | res = queue.delete(post_id) 48 | # p res 49 | refute_nil res 50 | assert !(res.msg.nil? || res.msg.empty?) 51 | end 52 | 53 | msg = queue.get 54 | # p msg 55 | assert_nil msg 56 | 57 | end 58 | puts "count: #{quicky.results(:post).count}" 59 | puts "avg post: #{quicky.results(:post).duration}" 60 | puts "avg get: #{quicky.results(:get).duration}" 61 | puts "avg delete: #{quicky.results(:delete).duration}" 62 | puts "queue size: #{queue.reload.size}" 63 | assert_equal 0, queue.size 64 | resp = queue.delete_queue 65 | assert_equal 200, resp.code, "API must respond with HTTP 200 status, but returned HTTP #{resp.code}" 66 | 67 | 68 | end 69 | 70 | end 71 | 72 | -------------------------------------------------------------------------------- /test/quick_run2.rb: -------------------------------------------------------------------------------- 1 | require 'quicky' 2 | require 'go' 3 | require 'pp' 4 | require File.expand_path('test_base.rb', File.dirname(__FILE__)) 5 | 6 | THREADS = 10 7 | TIMES_PER_THREAD = 1000 8 | 9 | class QuickRun < TestBase 10 | 11 | def setup 12 | super 13 | end 14 | 15 | def test_quick 16 | Go.logger.level = Logger::DEBUG 17 | Concur.logger.level = Logger::DEBUG 18 | ch = Go::Channel.new 19 | results = nil 20 | 21 | (THREADS-1).times do |ti| 22 | 23 | go do 24 | do_it(ch, ti) 25 | end 26 | 27 | end 28 | 29 | ch.each do |r| 30 | puts "results: #{r.inspect}" 31 | if results 32 | results.merge!(r) 33 | else 34 | results = r 35 | end 36 | end 37 | pp results.to_hash 38 | 39 | end 40 | 41 | def do_it(ch, ti) 42 | 43 | begin 44 | 45 | queue_name = "ironmq_gem_quick_#{ti}_#{rand(1000)}" 46 | clear_queue(queue_name) 47 | queue = @client.queue(queue_name) 48 | 49 | quicky = Quicky::Timer.new 50 | consumer_ch = Go::Channel.new 51 | quicky.loop(:test_quick, TIMES_PER_THREAD) do |i| 52 | puts "==== LOOP t#{ti} - #{i} ==================================" 53 | 54 | if i == 50 || i == TIMES_PER_THREAD-1 55 | start_consumer(quicky, queue, consumer_ch) 56 | end 57 | 58 | post_id = nil 59 | quicky.time(:post) do 60 | res = queue.post("hello world!") 61 | # p res 62 | refute_nil res 63 | refute_nil res.id 64 | post_id = res.id 65 | assert !(res.msg.nil? || res.msg.empty?) 66 | end 67 | 68 | end 69 | # wait for consumer to end 70 | #i = 0 71 | #consumer_ch.each do |r| 72 | # i+=1 73 | # puts "consumer #{r}" 74 | # consumer_ch.close if i == TIMES_PER_THREAD 75 | #end 76 | sleep TIMES_PER_THREAD / 2 77 | 78 | puts "count: #{quicky.results(:post).count}" 79 | puts "avg post: #{quicky.results(:post).duration}" 80 | puts "avg get: #{quicky.results(:get).duration}" 81 | puts "avg delete: #{quicky.results(:delete).duration}" 82 | puts "queue size: #{queue.reload.size}" 83 | resp = queue.delete_queue 84 | assert_equal 200, resp.code, "API must respond with HTTP 200 status, but returned HTTP #{resp.code}" 85 | 86 | ch << quicky.results 87 | 88 | rescue Exception => ex 89 | p ex 90 | p ex.backtrace 91 | end 92 | 93 | end 94 | 95 | def start_consumer(quicky, queue, channel) 96 | go do 97 | while true do 98 | msg = nil 99 | quicky.time(:get) do 100 | msg = queue.get 101 | end 102 | if msg.nil? 103 | channel << "done" 104 | break 105 | end 106 | quicky.time(:delete) do 107 | msg.delete 108 | end 109 | end 110 | end 111 | end 112 | 113 | end 114 | 115 | -------------------------------------------------------------------------------- /test/schedule_abt.rb: -------------------------------------------------------------------------------- 1 | require 'abt' 2 | require 'time' 3 | require 'test_base' 4 | 5 | # Config for abt tests to run on IronWorker 6 | @abt_config = YAML::load_file(File.expand_path(File.join("~", "Dropbox", "configs", "abt", "test", "config.yml"))) 7 | IronWorker.configure do |c| 8 | c.token = @abt_config['iron']['token'] 9 | c.project_id = @abt_config['iron']['project_id'] 10 | end 11 | 12 | # IronWorker.logger.level = Logger::DEBUG 13 | # Config to run iron_mq_ruby tests 14 | @test_config = TestBase.load_config 15 | 16 | worker = Abt::AbtWorker.new 17 | worker.git_url = "git://github.com/iron-io/iron_mq_ruby.git" 18 | worker.test_config = @test_config 19 | worker.add_notifier(:hip_chat_notifier, :config=>@abt_config["hipchat"]) 20 | worker.upload # Must upload after adding notifier to ensure it's merged 21 | #worker.run_local 22 | #worker.queue 23 | #status = worker.wait_until_complete 24 | #p status 25 | #puts "LOG:" 26 | #puts worker.get_log 27 | #worker.schedule(:start_at=>Time.now.iso8601, :run_every=>3600) 28 | -------------------------------------------------------------------------------- /test/test_base.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gem 'minitest' 3 | require 'minitest/autorun' 4 | require 'yaml' 5 | require 'uber_config' 6 | 7 | begin 8 | require File.expand_path('../lib/iron_mq', File.dirname(__FILE__)) 9 | rescue Exception => ex 10 | puts "Could NOT load current iron_mq: " + ex.message 11 | raise ex 12 | end 13 | 14 | LOG = Logger.new(STDOUT) 15 | LOG.level = Logger::INFO 16 | MAX_TRIES = 10 17 | 18 | class TestBase < Minitest::Test 19 | 20 | def setup 21 | puts 'setup' 22 | # check multiple config locations 23 | @config = UberConfig.load 24 | puts "config=" + @config.inspect 25 | 26 | config = @config['iron'] 27 | @host = "#{config['host'] || "mq-aws-us-east-1-1.iron.io"}" 28 | 29 | @client = IronMQ::Client.new(@config['iron']) 30 | puts "IronMQ::VERSION = #{IronMQ::VERSION}" 31 | # Rest.logger.level = Logger::DEBUG # this doesn't work for some reason? 32 | # IronCore::Logger.logger.level = Logger::DEBUG 33 | 34 | @queue_name = 'ironmq-ruby-tests' # default queue for tests 35 | end 36 | 37 | 38 | def clear_queue(queue_name=nil) 39 | queue_name ||= @queue_name 40 | 41 | queue = @client.queue(queue_name) 42 | 43 | puts "clearing queue #{queue_name}" 44 | begin 45 | queue.clear 46 | puts 'cleared.' 47 | rescue Rest::HttpError => ex 48 | if ex.code == 404 49 | # this is fine 50 | puts '404 ON CLEAR!!' 51 | else 52 | raise ex 53 | end 54 | end 55 | 56 | end 57 | 58 | def assert_performance(time) 59 | start_time = Time.now 60 | 61 | yield 62 | 63 | execution_time = Time.now - start_time 64 | 65 | assert execution_time < time, "Execution time too big #{execution_time.round(2)}, should be #{time}" 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /test/test_bulk.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('test_base.rb', File.dirname(__FILE__)) 2 | 3 | class TestBulk < TestBase 4 | 5 | def setup 6 | super 7 | 8 | end 9 | 10 | def test_bulk 11 | LOG.info "test_bulk" 12 | 13 | q_name = "ironmq-gem-bulk#{Time.now.to_i}" 14 | queue = @client.queue(q_name) 15 | 16 | times = 50 17 | t = Time.now 18 | times.times do |i| 19 | puts "Posting #{i}" 20 | res = queue.post("hello world #{i}!") 21 | end 22 | LOG.info "#{times} posts took #{Time.now.to_f - t.to_f}" 23 | 24 | t = Time.now 25 | res = nil 26 | while (res = queue.get()) 27 | del = res.delete 28 | end 29 | LOG.info "#{times} gets and deletes took #{Time.now.to_f - t.to_f}" 30 | 31 | # delete queue on test complete 32 | resp = queue.delete_queue 33 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 34 | end 35 | 36 | end 37 | 38 | -------------------------------------------------------------------------------- /test/test_iron_mq.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'yaml' 3 | require File.expand_path('test_base.rb', File.dirname(__FILE__)) 4 | 5 | class IronMQTests < TestBase 6 | def setup 7 | super 8 | LOG.info "@host: #{@host}" 9 | 10 | end 11 | 12 | def test_basics 13 | queue_name = 'test_basics_71' 14 | clear_queue(queue_name) 15 | 16 | # NOTE: Kept for backward compatibility checking 17 | queue = @client.queue(queue_name) 18 | res = queue.post("hello world!") 19 | # p res 20 | 21 | assert res["id"] 22 | assert res.id 23 | assert res.msg 24 | sleep 0.3 25 | assert_equal 1, queue.reload.size 26 | 27 | message = queue.reserve 28 | # p res 29 | assert res["id"] 30 | assert res.id 31 | 32 | res = queue.delete(res["id"], message.reservation_id) 33 | # p res 34 | res = queue.reserve 35 | # p res 36 | assert_nil res 37 | 38 | sleep 0.3 39 | assert_equal 0, queue.reload.size 40 | 41 | res = queue.post("hello world 2!") 42 | # p res 43 | 44 | msg = queue.reserve 45 | # p msg 46 | assert msg 47 | 48 | res = msg.delete 49 | #p res 50 | 51 | res = queue.reserve 52 | # p res 53 | assert_nil res 54 | 55 | # new style of referencing queue 56 | queue = @client.queue(queue_name) 57 | v = "hello big world" 58 | res = queue.post(v) 59 | # p res 60 | assert res.msg 61 | 62 | res = queue.reserve 63 | puts "queue.reserve got: #{res.body}" 64 | p res 65 | assert res["id"] 66 | assert res.id 67 | assert_equal v, res.body 68 | 69 | res = queue.delete(res.id, res.reservation_id) 70 | 71 | res = queue.reserve 72 | # p res 73 | assert_nil res 74 | 75 | # test delete by item 76 | res = queue.post(v) 77 | # p res 78 | assert res.msg 79 | 80 | res = queue.reserve 81 | # p res 82 | assert res.body 83 | 84 | res = res.delete 85 | # p res 86 | res = queue.reserve 87 | # p res 88 | assert_nil res 89 | 90 | # delete queue on test complete 91 | resp = queue.delete_queue 92 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 93 | end 94 | 95 | 96 | 97 | def test_multi_delete 98 | puts 'test_multi_delete' 99 | queue_name = 'test_multi_delete_41' 100 | clear_queue(queue_name) 101 | 102 | queue = @client.queue(queue_name) 103 | ids = [] 104 | 10.times do |i| 105 | msg = queue.post("hello #{i}") 106 | ids << {id: msg.id} 107 | end 108 | sleep 0.5 109 | assert_equal 10, queue.reload.size 110 | 111 | queue.delete_messages(ids) 112 | sleep 1 113 | assert_equal 0, queue.reload.size 114 | 115 | # now try it with reserved messages 116 | ids = [] 117 | 10.times do |i| 118 | msg = queue.post("hello #{i}") 119 | end 120 | sleep 0.5 121 | assert_equal 10, queue.reload.size 122 | while (msg = queue.reserve) != nil do 123 | ids << {id: msg.id, reservation_id: msg.reservation_id} 124 | end 125 | queue.delete_messages(ids) 126 | sleep 1 127 | assert_equal 0, queue.reload.size 128 | 129 | queue.delete_queue 130 | 131 | end 132 | 133 | def test_reservation_ids 134 | puts 'test_reservation_ids' 135 | # get a message, let it timeout, then try to delete it. That should fail. 136 | queue_name = 'test_res_ids' 137 | clear_queue(queue_name) 138 | 139 | queue = @client.queue(queue_name) 140 | msg = queue.post("hello") 141 | msg = queue.reserve(:timeout=>3) 142 | sleep 3 143 | ex = assert_raises Rest::HttpError do 144 | msg.delete 145 | end 146 | assert_equal 403, ex.code 147 | 148 | end 149 | 150 | def test_queues_list 151 | queue_name = 'test_queues_list_1' 152 | clear_queue(queue_name) 153 | 154 | queue = @client.queue(queue_name) 155 | res = queue.post("hello world!") 156 | # p res 157 | 158 | res = @client.queues.list 159 | assert res.size > 0, "project must contain at least one queue" 160 | 161 | res.each do |q| 162 | # puts "#{q.name} and #{queue_name}" 163 | if q.name == queue_name 164 | assert_equal q.size, 1 165 | end 166 | end 167 | 168 | # delete queue on test complete 169 | resp = queue.delete_queue 170 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 171 | end 172 | 173 | 174 | # TODO: pass :timeout in post/get messages and test those 175 | def test_timeout 176 | queue_name = "test_timeout_71" 177 | clear_queue(queue_name) 178 | 179 | queue = @client.queue(queue_name) 180 | 181 | res = queue.post("hello world timeout!") 182 | # p res 183 | 184 | msg = queue.get 185 | # p msg 186 | assert msg 187 | 188 | msg_nil = queue.get 189 | # p msg_nil 190 | assert_nil msg_nil 191 | 192 | sleep 61 # should be 1 minute timeout by default 193 | new_msg = queue.get 194 | refute_nil new_msg 195 | assert_equal new_msg.id, msg.id 196 | new_msg.delete 197 | 198 | # now try explicit timeout 199 | timeout = 5 200 | res = queue.post("hello world timeout2!", :timeout => timeout) 201 | # p resds 202 | msg = queue.get(:timeout=>timeout) 203 | # p msg 204 | assert msg 205 | # assert_equal 30, msg.timeout - removed in v3 206 | 207 | msg_nil = queue.get(:timeout=>timeout) 208 | # p msg_nil 209 | assert_nil msg_nil 210 | 211 | sleep timeout + 1 212 | new_msg = queue.get(:timeout=>timeout) 213 | refute_nil new_msg 214 | assert_equal new_msg.id, msg.id 215 | new_msg.delete 216 | 217 | # timeout on get 218 | res = queue.post("hello world timeout3!") 219 | msg = queue.get(:timeout => timeout) 220 | # puts "MESSAGE IS #{msg.inspect}" 221 | assert msg 222 | # assert_equal msg.timeout, 30 - removed in v3 223 | 224 | msg_nil = queue.get(:timeout=>timeout) 225 | # p msg_nil 226 | assert_nil msg_nil 227 | 228 | sleep timeout+1 229 | new_msg = queue.get 230 | refute_nil new_msg 231 | assert_equal new_msg.id, msg.id 232 | new_msg.delete 233 | 234 | # delete queue on test complete 235 | resp = queue.delete_queue 236 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 237 | end 238 | 239 | # todo: This test should be changed to make queues, it assumes 30+ queues are already created. 240 | def test_queues 241 | puts "test_queues-#{Time.now.to_i}" 242 | 243 | qname = "some_queue_that_does_not_exist_1" 244 | queue = @client.queue(qname) 245 | # create at least one queue 246 | queue.post('create queue message') 247 | # queue should exist now 248 | m = queue.get 249 | refute_nil m 250 | 251 | res = @client.queues.list(page: 1, per_page: 30) 252 | # puts "res.size: #{res.size}" 253 | assert_equal 30, res.size 254 | 255 | # delete queue on test complete 256 | resp = queue.delete_queue 257 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 258 | end 259 | 260 | def test_delay 261 | puts 'test_delay' 262 | 263 | queue_name = "test_delay_61" 264 | clear_queue(queue_name) 265 | 266 | msgTxt = "testMessage-"+Time.now.to_s 267 | # puts msgTxt 268 | queue = @client.queue(queue_name) 269 | msg_id = queue.post(msgTxt, {:delay => 5}).id 270 | msg = queue.get 271 | # p msg 272 | assert_nil msg 273 | 274 | sleep 6 275 | new_msg = queue.get 276 | refute_nil new_msg 277 | assert_equal msg_id, new_msg.id 278 | new_msg.delete 279 | 280 | # delete queue on test complete 281 | resp = queue.delete_queue 282 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 283 | end 284 | 285 | def test_batch 286 | puts 'test_batch' 287 | 288 | queue_name = "test_batch_61" 289 | clear_queue(queue_name) 290 | 291 | x = [] 292 | 10.times do |i| 293 | x << {:body => "body #{i}"} 294 | end 295 | 296 | queue = @client.queue(queue_name) 297 | 298 | resp = queue.post(x) 299 | assert resp["ids"] 300 | assert resp["ids"].is_a?(Array) 301 | assert_equal 10, resp["ids"].size 302 | 303 | msg = queue.get 304 | assert msg 305 | assert msg['id'] 306 | puts "Deleting message #{msg.id}" 307 | msg.delete 308 | sleep 2 309 | 310 | msgs = queue.get(:n => 10) 311 | assert msgs.is_a?(Array) 312 | msgs.each do |m| 313 | puts m.id 314 | refute_equal msg.id, m.id 315 | end 316 | assert msgs.size == 9, "size should be 9, but it's #{msgs.size}" 317 | assert msgs[0]["id"] 318 | 319 | msgs.each do |m| 320 | resp = m.delete 321 | assert_equal 200, resp.code, "API must delete message and response with HTTP 200 status, but returned HTTP #{resp.code}" 322 | end 323 | 324 | # delete queue on test complete 325 | resp = queue.delete_queue 326 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 327 | end 328 | 329 | def test_peek 330 | puts "test_message_peek" 331 | 332 | queue_name = "test_msg_peek_1" 333 | clear_queue(queue_name) 334 | 335 | queue = @client.queue(queue_name) 336 | queue.post("zero message") 337 | msg = queue.get 338 | msg.delete 339 | 340 | msg = queue.peek 341 | assert_nil msg 342 | 343 | queue.post("first message") 344 | sleep 1 345 | queue.post("second message") 346 | sleep 1 347 | queue.post("third message") 348 | 349 | msg = queue.peek 350 | refute_nil msg 351 | assert_equal "first message", msg.body, "message body must be 'first message', but it's '#{msg.body}'" 352 | 353 | msg = queue.peek 354 | refute_nil msg 355 | assert_equal "first message", msg.body, "message body must be 'first message', but it's '#{msg.body}'" 356 | 357 | msgs = queue.peek(:n => 2) 358 | assert_equal Array, msgs.class, "waiting for Array, but got #{msgs.class}" 359 | assert_equal 2, msgs.size, "must received 2 messages, but received #{msgs.size}" 360 | 361 | msg = queue.peek 362 | refute_nil msg 363 | assert_equal "first message", msg.body, "message body must be 'first message', but it's '#{msg.body}'" 364 | 365 | msgs = queue.peek(:n => 7) 366 | assert_equal Array, msgs.class, "waiting for Array, but got #{msgs.class}" 367 | assert_equal 3, msgs.size, "must received 3 messages, but received #{msgs.size}" 368 | 369 | msg = queue.get 370 | refute_nil msg 371 | assert_equal "first message", msg.body, "message body must be 'first message', but it's '#{msg.body}'" 372 | 373 | resp = msg.delete 374 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 375 | 376 | msg = queue.peek 377 | refute_nil msg 378 | assert_equal "second message", msg.body, "message body must be 'second message', but it's '#{msg.body}'" 379 | 380 | # delete queue on test complete 381 | resp = queue.delete_queue 382 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 383 | end 384 | 385 | def test_touch 386 | puts "in test_touch" 387 | 388 | queue_name = "test_msg_touch_3" 389 | clear_queue(queue_name) 390 | 391 | queue = @client.queue(queue_name) 392 | queue.post("first message") 393 | queue.post("second message") 394 | queue.post("third message") 395 | 396 | 397 | # get message 398 | msg = queue.get(:timeout => 10) 399 | refute_nil msg 400 | assert_equal "first message", msg.body, "message body must be 'first message', but it's '#{msg.body}'" 401 | 402 | sleep 5 # timeout is not passed 403 | 404 | msgs = queue.peek(:n => 3) # all messages from queue 405 | assert_equal Array, msgs.class, "waiting for Array, but got #{msgs.class}" 406 | assert_equal 2, msgs.size, "API must return only 2 messages" 407 | msgs.each do |m| 408 | refute_equal msg.id, m.id, "returned a message which must be reserved" 409 | end 410 | 411 | sleep 5.5 # ensure timeout is passed 412 | 413 | # message must return to the queue 414 | msgs = queue.peek(:n => 3) 415 | assert_equal Array, msgs.class, "waiting for Array, but got #{msgs.class}" 416 | assert_equal 3, msgs.size, "API must return 3 messages" 417 | 418 | msg = queue.get(:timeout=>10) 419 | refute_nil msg 420 | assert_equal "second message", msg.body, "message body must be 'second message', but it's '#{msg.body}'" 421 | 422 | sleep 5 # timeout is not passed 423 | 424 | msgs = queue.peek(:n => 3) # must return another message 425 | assert_equal Array, msgs.class, "waiting for Array, but got #{msgs.class}" 426 | assert_equal 2, msgs.size, "API must return only 2 messages" 427 | msgs.each { |m| refute_equal msg.id, m.id, "returned message which must be reserved" } 428 | 429 | resp = msg.touch # (:timeout=>10) # increase timeout again, should be another 10 seconds 430 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 431 | 432 | sleep 5 # new timeout is not passed, but previous is (15 + 20 vs 30 + 30 seconds) 433 | 434 | msgs = queue.peek(:n => 3) # must return the same as for msg2 435 | assert_equal Array, msgs.class, "waiting for Array, but got #{msgs.class}" 436 | assert_equal 2, msgs.size, "API must return only 2 messages" 437 | msgs.each { |m| refute_equal msg.id, m.id, "returned message which must be reserved" } 438 | 439 | sleep 5 # ensure timeout passed 440 | queue.clear 441 | queue.post("first message") 442 | queue.post("second message") 443 | queue.post("third message") 444 | 445 | # message must be returned to the end of the queue 446 | msgs = queue.peek(:n => 3) 447 | msg = queue.reserve 448 | assert_equal Array, msgs.class, "waiting for Array, but got #{msgs.class}" 449 | assert_equal 3, msgs.size, "API must return 3 messages" 450 | assert_equal msg.id, msgs[0].id, "released message must be at the beginning of the queue" 451 | 452 | # delete queue on test complete 453 | resp = queue.delete_queue 454 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 455 | end 456 | 457 | def test_release 458 | puts 'test_release' 459 | 460 | queue_name = "test_release_#{Time.now.to_i}" 461 | clear_queue(queue_name) 462 | 463 | msg_txt = "testMessage-#{Time.now.to_i}" 464 | # puts msgTxt 465 | 466 | queue = @client.queue(queue_name) 467 | 468 | msg_id = queue.post(msg_txt, {:timeout => 60*5}).id 469 | # puts "msg_id: #{msg_id}" 470 | message = queue.reserve 471 | # p msg 472 | assert_equal msg_id, message.id 473 | # Ok, so should have received same message, now let's release it quicker than the original timeout 474 | 475 | # but first, ensure the next get is nil 476 | msg = queue.reserve 477 | # p msg 478 | assert_nil msg 479 | 480 | # now release it instantly 481 | re = message.release 482 | msg = queue.reserve 483 | 484 | assert msg.raw 485 | assert_equal msg_id, msg.id 486 | 487 | # let's release it in 10 seconds 488 | msg.release(:delay => 10) 489 | msgr = queue.reserve 490 | # p msg 491 | assert_nil msgr 492 | 493 | sleep 11 494 | msg = queue.reserve 495 | refute_nil msg 496 | assert_equal msg_id, msg.id 497 | 498 | msg.release(:delay => 5) 499 | msg = queue.reserve 500 | # p msg 501 | assert_nil msg 502 | 503 | sleep 6 504 | msg = queue.reserve 505 | refute_nil msg 506 | assert_equal msg_id, msg.id 507 | 508 | # delete queue on test complete 509 | resp = queue.delete_queue 510 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 511 | end 512 | 513 | def test_clear 514 | puts "test_clear" 515 | 516 | queue = @client.queue("test_clear_9") 517 | clear_queue(queue.name) 518 | 519 | val = "hi mr clean" 520 | queue.post(val) 521 | 522 | sleep 0.5 # make sure the counter has time to update 523 | assert_equal 1, queue.reload.size 524 | 525 | queue.clear 526 | 527 | msg = queue.get 528 | assert_nil msg 529 | 530 | sleep 0.5 531 | 532 | assert_equal 0, queue.reload.size 533 | 534 | # delete queue on test complete 535 | resp = queue.delete_queue 536 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 537 | end 538 | 539 | 540 | def test_poll 541 | queue_name = "test_poll_6" 542 | clear_queue(queue_name) 543 | 544 | queue = @client.queue(queue_name) 545 | 546 | v = "hello world" 547 | 5.times { queue.post(v) } 548 | 549 | i = 0 550 | queue.poll(:break_if_nil => true) do |msg| 551 | assert msg.body.include?("hello") 552 | i += 1 553 | end 554 | 555 | assert_equal 5, i 556 | 557 | # delete queue on test complete 558 | resp = queue.delete_queue 559 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 560 | end 561 | 562 | def test_queue_delete 563 | queue = @client.queue("test_delete") 564 | queue.post("hi") 565 | queue.delete_queue 566 | 567 | LOG.info "sleeping for a bit to let queue delete..." 568 | sleep 60 569 | 570 | queue.post("hi2") 571 | # p queue 572 | assert_equal 1, queue.size, "queue size must be 1, but got #{queue.size}" 573 | 574 | msg = queue.get 575 | assert_equal "hi2", msg.body, "message body must be 'hi2', but got '#{msg.body}'" 576 | 577 | # delete queue on test complete 578 | resp = queue.delete_queue 579 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 580 | end 581 | 582 | def test_webhooks 583 | qname ="webhook_queue" 584 | url = "#{@client.base_url}/#{qname}/webhook?oauth=#{@client.token}" 585 | # p url 586 | 587 | v = "hello webhook" 588 | 589 | @rest = Rest::Client.new 590 | resp = @rest.post(url, :body => v) 591 | # p resp 592 | 593 | queue = @client.queue(qname) 594 | msg = queue.get 595 | # p msg 596 | assert_equal v, msg.body 597 | 598 | # delete queue on test complete 599 | resp = queue.delete_queue 600 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 601 | end 602 | 603 | 604 | def test_queue_params 605 | 606 | qname = "test_queue_params_1" 607 | 608 | clear_queue(qname) 609 | q = @client.queue(qname) 610 | 611 | q.post("message 1", :timeout => 200, :delay => 0, :expires_in => 2000) 612 | q.post("message 1", :timeout => 300, :delay => 0, :expires_in => 3000) 613 | 614 | msgs = q.get(:n => 2) 615 | 616 | msgs.each do |m| 617 | puts m.body 618 | puts "timeout: #{m.timeout}" 619 | puts "expires_in: #{m.expires_in}" 620 | puts "delay: #{m.delay}" 621 | end 622 | 623 | end 624 | 625 | 626 | def test_reserved_count 627 | puts "test_reserved_count" 628 | 629 | queue_name = "test_reserved_count" 630 | clear_queue(queue_name) 631 | 632 | queue = @client.queue(queue_name) 633 | queue.post("zero message") 634 | msg = queue.get 635 | p msg 636 | puts "id: #{msg.id} reserved_count: #{msg.reserved_count}" 637 | msg.release 638 | msg = queue.get 639 | p msg 640 | puts "id: #{msg.id} reserved_count: #{msg.reserved_count}" 641 | msg.release 642 | msg = queue.get 643 | p msg 644 | puts "id: #{msg.id} reserved_count: #{msg.reserved_count}" 645 | 646 | 647 | end 648 | 649 | def test_queue_set_info 650 | qname = "test_queue_set_info_#{Time.now.to_i}" 651 | clear_queue(qname) 652 | q = @client.queue(qname) 653 | q.update(message_timeout: 45) 654 | assert_equal 45, q.reload.info['message_timeout'] 655 | q.update(message_expiration: 3000) 656 | assert_equal 3000, q.reload.info['message_expiration'] 657 | end 658 | 659 | def test_dequeue_delete 660 | queue_name = "test_dequeue_delete_#{Time.now.to_i}" 661 | clear_queue(queue_name) 662 | queue = @client.queue(queue_name) 663 | v = "hello thou shalt only see me once" 664 | queue.post(v) 665 | msg = queue.get(delete: true, timeout: 30) 666 | assert_equal msg.body, "hello thou shalt only see me once" 667 | sleep 1 668 | # get the queue again 669 | queue = @client.queue(queue_name) 670 | assert_equal 0, queue.size 671 | sleep 31 672 | msg = queue.get 673 | assert_equal nil, msg 674 | end 675 | 676 | def test_long_polling 677 | queue_name = "test_long_polling#{Time.now.to_i}" 678 | queue = @client.queue(queue_name) 679 | queue.update 680 | msg = queue.get 681 | assert_nil msg 682 | v = "hello long" 683 | # ok, nothing in the queue, let's do a long poll 684 | starti = Time.now.to_i 685 | thr = Thread.new { 686 | sleep 5 687 | puts "Posting now" 688 | begin 689 | queue.post(v) 690 | rescue Exception => ex 691 | p ex 692 | end 693 | 694 | } 695 | puts "Now going to wait for it..." 696 | msg = queue.get(wait: 20) 697 | # p msg 698 | endi = Time.now.to_i 699 | duration = endi - starti 700 | p duration 701 | assert duration > 4 && duration <= 7 702 | refute_nil msg 703 | assert_equal v, msg.body 704 | msg.delete 705 | end 706 | 707 | def test_delete_reserved_messages 708 | queue_name = 'test_delete_reserved_messages' 709 | queue = @client.queue(queue_name) 710 | clear_queue(queue_name) 711 | queue.post("more") 712 | queue.post("and more") 713 | queue.post("and more") 714 | assert_equal 3, queue.size 715 | messages = queue.reserve(n: 3) 716 | queue.delete_reserved_messages(messages) 717 | assert_equal 0, queue.reload.size 718 | end 719 | 720 | def test_delete_reserved_message 721 | queue_name = 'test_delete_message' 722 | queue = @client.queue(queue_name) 723 | clear_queue(queue_name) 724 | queue.post("test message") 725 | assert_equal 1, queue.reload.size 726 | message = queue.reserve 727 | queue.delete(message.id, message.reservation_id) 728 | assert_equal 0, queue.reload.size 729 | 730 | queue.post("another message") 731 | assert_equal 1, queue.reload.size 732 | message = queue.reserve 733 | message.delete 734 | assert_equal 0, queue.reload.size 735 | end 736 | end 737 | 738 | -------------------------------------------------------------------------------- /test/test_mq_worker_subscribers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # This test requires a hello worker in your project, run next line to add one: 3 | # iron_worker upload https://github.com/treeder/hello_worker/blob/master/hello.worker --config config.yml 4 | # 5 | 6 | require File.expand_path('test_base.rb', File.dirname(__FILE__)) 7 | require 'logger' 8 | require 'iron_worker' 9 | 10 | 11 | class TestWorkerSubscribers < TestBase 12 | 13 | def setup 14 | super 15 | end 16 | 17 | def make_key(i, t, random=0) 18 | key = "pushq-#{t}-#{i}-#{random}" 19 | end 20 | 21 | def test_mq_worker_subscribers 22 | 23 | publisher_name = "publisher" 24 | receiver_name = "push_receiver" 25 | code_name = "hello" 26 | queue = @client.queue(publisher_name) 27 | receiver_queue = @client.queue(receiver_name) 28 | clear_queue(receiver_name) 29 | 30 | # test for bad subscribers 31 | puts "raising..." 32 | assert_raises Rest::HttpError do 33 | # can't subscribe to self 34 | res = queue.update_queue(:subscribers => [{:url => "ironmq:///#{publisher_name}"}]) 35 | end 36 | 37 | assert_raises Rest::HttpError do 38 | # must have a token if sending to different project_id 39 | res = queue.update_queue(:subscribers => [{:url => "ironmq://ABCDEFG@/somerandomqueue"}]) 40 | end 41 | 42 | subscribers = [] 43 | subscribers << {:url => "ironmq:///#{receiver_name}"} 44 | subscribers << {:url => "ironworker:///#{code_name}"} 45 | 46 | res = queue.update_queue(:subscribers => subscribers) 47 | 48 | LOG.debug queue.subscribers 49 | assert_equal subscribers.size, queue.subscribers.size 50 | 51 | body = "Hello IronMQ pusher!" 52 | m = queue.post(body) 53 | 54 | sleep 5 55 | # now check that there's a message in the queue and that the worker got a job 56 | receiver_queue.reload 57 | assert_equal 1, receiver_queue.size 58 | m2 = receiver_queue.get 59 | assert_equal body, m2.body 60 | m2.delete 61 | 62 | 63 | wc = @config['iron'] 64 | wc[:host] = wc[:worker_host] if wc[:worker_host] 65 | iron_worker = IronWorker::Client.new(wc) 66 | tasks = iron_worker.tasks.list(:code_name=>code_name, :from_time=>(Time.now - 30).to_i) 67 | assert_equal 1, tasks.size 68 | end 69 | 70 | 71 | end 72 | -------------------------------------------------------------------------------- /test/test_performance.rb: -------------------------------------------------------------------------------- 1 | gem 'test-unit' 2 | require 'test/unit' 3 | require 'yaml' 4 | require 'parallel' 5 | require_relative 'test_base' 6 | 7 | class TmpTests < TestBase 8 | def setup 9 | super 10 | 11 | end 12 | 13 | def test_performance_post_100_messages 14 | queue = @client.queue('test_perf_100') 15 | # slower to rackspace since this is running on aws 16 | timeout = @host.include?('rackspace') ? 40 : 12 17 | 18 | assert_performance(timeout) do 19 | 100.times do 20 | queue.post("hello world!") 21 | end 22 | 23 | # delete queue on test complete 24 | resp = queue.delete_queue 25 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 26 | end 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /test/test_push_queues.rb: -------------------------------------------------------------------------------- 1 | # Put config.yml file in ~/Dropbox/configs/ironmq_gem/test/config.yml 2 | require File.expand_path('test_base.rb', File.dirname(__FILE__)) 3 | require 'logger' 4 | 5 | class TestPushQueues < TestBase 6 | 7 | def setup 8 | super 9 | end 10 | 11 | def make_key(i, t, random=0) 12 | key = "pushq-#{t}-#{i}-#{random}" 13 | end 14 | 15 | def test_subscriber_add_remove 16 | puts "test_subscriber_add_remove" 17 | qname = "subscriber_#{Time.now.to_i}" 18 | first_subscriber = {name: "first_subscriber", url: "http://nowhere.com:8080/somepath"} 19 | second_subscriber = {name: "second_subscriber", url: "http://somewhere.com"} 20 | subscribers = [first_subscriber, second_subscriber] 21 | @client.create_queue(qname, push: {subscribers: subscribers}) 22 | queue = @client.queue(qname) 23 | LOG.debug queue.subscribers 24 | assert_equal 2, queue.subscribers.size 25 | 26 | queue.reload 27 | assert_equal 2, queue.subscribers.size 28 | p queue.subscribers[0].url 29 | queue.remove_subscribers([{name: first_subscriber[:name]}]) 30 | queue.reload 31 | assert_equal 1, queue.subscribers.size 32 | p queue.subscribers 33 | 34 | # add it back with add 35 | queue.add_subscriber(first_subscriber) 36 | queue.reload 37 | assert_equal 2, queue.subscribers.size 38 | p queue.subscribers[0].url 39 | queue.remove_subscriber(first_subscriber) 40 | queue.reload 41 | assert_equal 1, queue.subscribers.size 42 | p queue.subscribers 43 | 44 | # add two, remove first 45 | queue.add_subscriber(first_subscriber) 46 | queue.reload 47 | assert_equal 2, queue.subscribers.size 48 | p queue.subscribers[0].url 49 | queue.remove_subscriber(first_subscriber) 50 | queue.reload 51 | assert_equal 1, queue.subscribers.size 52 | p queue.subscribers[0].url 53 | assert_equal second_subscriber[:url], queue.subscribers[0].url 54 | 55 | queue.delete_queue 56 | end 57 | 58 | 59 | def test_queue_subscriptions 60 | puts "test_queue_subscriptions" 61 | types = ["multicast", "unicast"] 62 | # to delete queues later (clear project) 63 | queue_names = [] 64 | types.each do |t| 65 | 66 | LOG.info "Trying type #{t}" 67 | 68 | qname = "subscription-queue-#{Time.now.to_i}" 69 | queue_names << qname 70 | 71 | num_subscribers = 10 72 | subscribers = [] 73 | 74 | x = rand(1000) 75 | num_subscribers.times do |i| 76 | key = make_key(i, t, x) 77 | subscribers << {url: "http://rest-test.iron.io/code/200?store=#{key}", 78 | name: "name_#{key}"} 79 | end 80 | 81 | @client.create_queue(qname, {type: t, push: {subscribers: subscribers}}) 82 | queue = @client.queue(qname) 83 | 84 | LOG.debug queue.subscribers 85 | assert_equal num_subscribers, queue.subscribers.size 86 | 87 | # add the last one 88 | queue.reload # temporary, can remove 89 | subscriber_name = "name_#{Time.now.to_i}" 90 | queue.add_subscriber({url: "http://nowhere.com", name: subscriber_name}) 91 | queue.reload 92 | assert_equal num_subscribers + 1, queue.subscribers.size 93 | queue.remove_subscriber({name: subscriber_name}) 94 | queue.reload 95 | assert_equal num_subscribers, queue.subscribers.size 96 | 97 | # todo: assert subscriptions match 98 | 99 | msg = "hello #{x}" 100 | m = queue.post(msg) 101 | 102 | LOG.info "Checking results..." 103 | @rest = Rest::Client.new 104 | found = 0 105 | if t == "multicast" 106 | num_subscribers.times do |i| 107 | key = make_key(i, t, x) 108 | tries = MAX_TRIES 109 | while tries > 0 110 | tries -= 1 111 | sleep 1 112 | begin 113 | url = "http://rest-test.iron.io/stored/#{key}" 114 | LOG.info "checking url #{url}" 115 | response = @rest.get(url) 116 | p response.body 117 | parsed = JSON.parse(response.body) 118 | LOG.debug parsed['body'] 119 | assert_equal msg, parsed['body'] 120 | found += 1 121 | break 122 | rescue Rest::HttpError => ex 123 | LOG.debug ex.code 124 | assert_equal 404, ex.code 125 | end 126 | end 127 | refute_equal tries, 0 128 | end 129 | elsif t == "unicast" 130 | tries = MAX_TRIES 131 | while tries > 0 132 | tries -= 1 133 | sleep 0.5 134 | num_subscribers.times do |i| 135 | key = make_key(i, t, x) 136 | begin 137 | url = "http://rest-test.iron.io/stored/#{key}" 138 | LOG.info "checking url #{url}" 139 | response = @rest.get(url) 140 | parsed = JSON.parse(response.body) 141 | LOG.debug parsed['body'] 142 | assert_equal msg, parsed['body'] 143 | found += 1 144 | break 145 | rescue Rest::HttpError => ex 146 | LOG.debug ex.code 147 | assert_equal 404, ex.code 148 | end 149 | end 150 | break if found == 1 151 | end 152 | refute_equal tries, 0 153 | end 154 | 155 | tries = MAX_TRIES 156 | while tries > 0 157 | 158 | # Need to wait > 60s here, because in case of retries on pusherd 159 | # side (due lost connection for example) there will be no response earlier 160 | # (default retries_delay is 60s). 161 | sleep 1 162 | tries -= 1 163 | # old style of message getting 164 | msg = queue.messages.get_message(m.id) 165 | LOG.info "checking for message: #{msg}" 166 | next if msg.nil? 167 | subscribers = msg.subscribers 168 | 169 | LOG.debug subscribers 170 | if t == "unicast" 171 | assert_equal 1, found 172 | assert_equal num_subscribers, subscribers.size 173 | else # pubsub 174 | assert_equal num_subscribers, found 175 | assert_equal num_subscribers, subscribers.size 176 | end 177 | 178 | do_retry = false 179 | subscribers.each do |s| 180 | LOG.debug s 181 | LOG.info "status_code=#{s['status_code']}" 182 | LOG.info "status=#{s['status']}" 183 | do_retry = true unless 200 == s["status_code"] 184 | do_retry = true unless "deleted" == s["status"] 185 | end 186 | next if do_retry 187 | break 188 | end 189 | assert_equal tries, 0 190 | 191 | # delete queue after all tests on it were completed 192 | resp = queue.delete_queue 193 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 194 | end 195 | end 196 | 197 | def test_headers 198 | puts "test_headers" 199 | qname = "push-headers_#{Time.now.to_i}" 200 | subscribers = [] 201 | 202 | x = rand(1000) 203 | key = "somemsg_#{x}" 204 | subscribers << {url: "http://rest-test.iron.io/code/200?store=#{key}", 205 | name: "name_#{key}", 206 | headers: {"Content-Type"=>"application/json"}} 207 | 208 | @client.create_queue(qname, {type: 'multicast', 209 | push: {subscribers: subscribers}}) 210 | 211 | queue = @client.queue(qname) 212 | LOG.debug queue.subscribers 213 | assert_equal subscribers.size, queue.subscribers.size 214 | queue.reload.subscribers.each do |s| 215 | p s.headers 216 | refute_nil s.headers['Content-Type'] 217 | end 218 | 219 | msg = "{\"hello\": #{x}}" 220 | m = queue.post(msg) 221 | 222 | LOG.info "Checking results..." 223 | @rest = Rest::Client.new 224 | found = 0 225 | tries = MAX_TRIES 226 | while tries > 0 227 | tries -= 1 228 | sleep 1 229 | begin 230 | url = "http://rest-test.iron.io/stored/#{key}" 231 | LOG.info "checking url #{url}" 232 | response = @rest.get(url) 233 | p response.body 234 | parsed = JSON.parse(response.body) 235 | LOG.debug parsed['body'] 236 | assert_equal msg, parsed['body'] 237 | refute_nil parsed['headers']['Content-Type'] 238 | assert_equal 'application/json', parsed['headers']['Content-Type'] 239 | break 240 | rescue Rest::HttpError => ex 241 | LOG.debug ex.code 242 | assert_equal 404, ex.code 243 | end 244 | refute_equal tries, 0 245 | end 246 | 247 | # delete queue after all tests on it were completed 248 | resp = queue.delete_queue 249 | assert_equal 200, resp.code, "API must respond with HTTP 200 status, but returned HTTP #{resp.code}" 250 | end 251 | 252 | 253 | def test_failure 254 | @rest = Rest::Client.new 255 | 256 | x = rand(1000) 257 | qname = "failure-queue_#{x}" 258 | 259 | subscribers = [] 260 | subscribers << {url: "http://rest-test.iron.io/code/503?switch_after=2&switch_to=200&namespace=push-test-failures-#{x}", 261 | name: "name#{x}"} 262 | subscribers << {url: "http://rest-test.iron.io/code/503", 263 | name: "name_#{Time.now.to_i}"} 264 | 265 | num_subscribers = 2 266 | 267 | @client.create_queue(qname, {type: 'multicast', 268 | push: { 269 | subscribers: subscribers, 270 | retries: 3, 271 | retries_delay: 3 272 | }}) 273 | queue = @client.queue(qname) 274 | LOG.debug queue 275 | LOG.debug queue.subscribers 276 | assert_equal num_subscribers, queue.subscribers.size 277 | 278 | msg = "hello #{x}" 279 | m = queue.post(msg) 280 | LOG.debug m 281 | 282 | tries = MAX_TRIES 283 | while tries > 0 284 | sleep 0.5 285 | tries -= 1 286 | LOG.info 'getting status' 287 | subscribers = queue.get_message(m.id).subscribers 288 | LOG.debug subscribers 289 | LOG.info "num_subscribers=#{num_subscribers} subscribers.size=#{subscribers.size}" 290 | 291 | assert_equal num_subscribers, subscribers.size 292 | do_retry = false 293 | subscribers.each do |s| 294 | LOG.debug s 295 | LOG.info "status_code=#{s['status_code']}" 296 | LOG.info "status=#{s['status']}" 297 | do_retry = true unless 503 == s["status_code"] 298 | do_retry = true unless ["reserved", "retrying"].include? s["status"] 299 | end 300 | next if do_retry 301 | break 302 | end 303 | assert_equal tries, 0 304 | 305 | tries = MAX_TRIES 306 | while tries > 0 307 | puts 'sleeping for 5 to wait for retry' 308 | sleep 5 309 | tries -= 1 310 | subscribers = queue.get_message(m.id).subscribers 311 | LOG.debug subscribers 312 | assert_equal num_subscribers, subscribers.size 313 | do_retry = false 314 | subscribers.each do |s| 315 | LOG.debug s 316 | if s["url"] == "http://rest-test.iron.io/code/503" 317 | if "error" == s["status"] 318 | assert_equal 0, s["retries_remaining"] 319 | else 320 | assert_equal 503, s["status_code"] 321 | do_retry = true 322 | end 323 | else 324 | # this one should error a couple times, then be successful 325 | LOG.info "retries_remaining: #{s["retries_remaining"]}" 326 | if ["deleted", "error"].include? s["status"] || 200 == s["status_code"] 327 | assert_equal 0, s["retries_remaining"] 328 | else 329 | do_retry = true 330 | end 331 | end 332 | end 333 | next if do_retry 334 | break 335 | end 336 | assert_equal tries, 0 337 | 338 | # delete queue on test complete 339 | resp = queue.delete_queue 340 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 341 | end 342 | 343 | 344 | def test_202 345 | puts "test_202_#{Time.now.to_i}" 346 | types = ["multicast"] 347 | types.each do |t| 348 | 349 | LOG.info "Trying type #{t}" 350 | 351 | qname = "subscription-queue-#{Time.now.to_i}" 352 | 353 | num_subscribers = 2 354 | subscribers = [] 355 | 356 | x = rand(1000) 357 | num_subscribers.times do |i| 358 | key = make_key(i, t, x) 359 | subscribers << {url: "http://test.iron.io/code/202?store=#{key}", name: "name_#{key}"} 360 | end 361 | 362 | res = @client.create_queue(qname, {type: t, 363 | push: {subscribers: subscribers}}) 364 | queue = @client.queue(qname) 365 | 366 | queue.reload 367 | LOG.debug queue 368 | queue = @client.queue(qname) 369 | 370 | assert_equal num_subscribers, queue.subscribers.size 371 | assert_equal t, queue.type 372 | puts "queue.push_type: #{queue.type}" 373 | # todo: assert subscriptions match 374 | 375 | msg = "hello #{x}" 376 | m = queue.post(msg, {:timeout => 2}) 377 | 378 | tries = MAX_TRIES 379 | while tries > 0 380 | sleep 0.5 381 | tries -= 1 382 | subscribers = queue.get_message(m.id).subscribers 383 | LOG.debug subscribers 384 | assert_equal num_subscribers, subscribers.size 385 | do_retry = false 386 | subscribers.each do |s| 387 | LOG.debug s 388 | do_retry = true unless 202 == s["status_code"] 389 | do_retry = true unless "reserved" == s["status"] 390 | end 391 | next if do_retry 392 | break 393 | end 394 | assert_equal tries, 0 395 | 396 | LOG.info 'sleeping 2' 397 | sleep 2 398 | 399 | tries = MAX_TRIES 400 | while tries > 0 401 | sleep 0.5 402 | tries -= 1 403 | subscribers = queue.get_message(m.id).subscribers 404 | LOG.debug subscribers 405 | assert_equal num_subscribers, subscribers.size 406 | assert_equal t, queue.type 407 | 408 | do_retry = false 409 | subscribers.each do |s| 410 | LOG.debug s 411 | LOG.info "status_code=#{s['status_code']}" 412 | LOG.info "status=#{s['status']}" 413 | 414 | do_retry = true unless 202 == s["status_code"] 415 | do_retry = true unless "reserved" == s["status"] 416 | end 417 | next if do_retry 418 | 419 | # now let's delete it to say we're done with it 420 | subscribers.each do |s| 421 | LOG.debug s 422 | LOG.info "status_code=#{s['status_code']}" 423 | LOG.info "status=#{s['status']}" 424 | LOG.info "Acking subscriber" 425 | res = s.delete 426 | LOG.debug res 427 | end 428 | break 429 | end 430 | refute_equal 0, tries 431 | 432 | tries = MAX_TRIES 433 | while tries > 0 434 | sleep 0.5 435 | tries -= 1 436 | subscribers = queue.get_message(m.id).subscribers 437 | LOG.debug subscribers 438 | next unless num_subscribers == subscribers.size 439 | 440 | do_retry = false 441 | subscribers.each do |s| 442 | LOG.debug s 443 | LOG.info "status=#{s['status']}" 444 | do_retry = true unless "deleted" == s["status"] 445 | end 446 | next if do_retry 447 | break 448 | end 449 | refute_equal 0, tries 450 | 451 | # delete queue on test complete 452 | resp = queue.delete_queue 453 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 454 | end 455 | end 456 | 457 | def test_post_and_instantiate 458 | qname = "push_and_instantiate#{Time.now.to_i}" 459 | 460 | subscribers = [{:url => "http://rest-test.iron.io/code/200", name: "name#{Time.now.to_i}"}, 461 | {:url => "http://rest-test.iron.io/code/200",name: "name#{Time.now.to_i}"}] 462 | 463 | res = @client.create_queue(qname, {type: 'multicast', 464 | push: {subscribers: subscribers}}) 465 | 466 | queue = @client.queue(qname) 467 | expected_size = subscribers.size 468 | got_size = queue.subscribers.size 469 | assert_equal expected_size, got_size, "queue's subscribers list must contain #{expected_size} elements, but got #{got_size}" 470 | 471 | msgs = queue.post([{:body => 'push'}, 472 | {:body => 'me'}, 473 | {:body => 'now'}], :instantiate => true) 474 | msgs.each { |msg| assert_instance_of(IronMQ::Message, msg, "post(:instantiate => true) must instantiate messages") } 475 | 476 | sleep 5 477 | 478 | resp = queue.delete_queue 479 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 480 | end 481 | 482 | def test_error_queues 483 | @rest = Rest::Client.new 484 | qname = "badrobot#{Time.now.to_i}" 485 | error_queue_name = "#{qname}--errors" 486 | clear_queue(qname) 487 | clear_queue(error_queue_name) 488 | 489 | x = rand(1000) 490 | 491 | subscribers = [] 492 | subscribers << {:url => "http://rest-test.iron.io/code/503", name: "name_#{Time.now.to_i}"} 493 | subscriber_urls = subscribers 494 | num_subscribers = subscribers.size 495 | 496 | res = @client.create_queue(qname, push: { 497 | subscribers: subscribers, 498 | retries: 3, 499 | retries_delay: 3, 500 | error_queue: error_queue_name 501 | }) 502 | queue = @client.queue(qname) 503 | msg = "hello #{x}" 504 | puts "Pushing msg: #{msg}" 505 | m = queue.post(msg) 506 | orig_id = m.id 507 | puts "Msg id on post: #{orig_id}" 508 | LOG.debug m 509 | 510 | tries = MAX_TRIES 511 | while tries > 0 512 | puts 'sleeping for 5 to wait for retry' 513 | sleep 5 514 | tries -= 1 515 | subscribers = queue.get_message(m.id).subscribers 516 | LOG.debug subscribers 517 | assert_equal num_subscribers, subscribers.size 518 | do_retry = false 519 | subscribers.each do |s| 520 | LOG.debug s 521 | if s["url"] == "http://rest-test.iron.io/code/503" 522 | if "error" == s["status"] 523 | assert_equal 0, s["retries_remaining"] 524 | else 525 | assert_equal 503, s["status_code"] 526 | do_retry = true 527 | end 528 | else 529 | # this one should error a couple times, then be successful 530 | LOG.info "retries_remaining: #{s["retries_remaining"]}" 531 | if ["deleted", "error"].include? s["status"] || 200 == s["status_code"] 532 | assert_equal 0, s["retries_remaining"] 533 | else 534 | do_retry = true 535 | end 536 | end 537 | end 538 | next if do_retry 539 | break 540 | end 541 | 542 | # check that the failed messages is in the error queue 543 | error_queue = @client.queue(error_queue_name) 544 | em = error_queue.get 545 | refute_nil em 546 | puts "rawbody: " + em.body 547 | error_hash = JSON.parse(em.body) 548 | assert error_hash['subscribers'] 549 | assert_equal subscriber_urls[0][:url], error_hash['subscribers'][0]['url'] 550 | assert_equal 503, error_hash['subscribers'][0]['code'] 551 | assert_equal orig_id, error_hash['source_msg_id'] 552 | refute_nil error_hash['subscribers'][0]['msg'] 553 | em.delete 554 | 555 | # now let's get the original message 556 | orig_msg = queue.get_message(error_hash['source_msg_id']) 557 | puts "orig_msg:" 558 | p orig_msg 559 | p orig_msg.body 560 | assert msg, orig_msg.body 561 | 562 | error_queue.delete_queue 563 | # delete queue on test complete 564 | resp = queue.delete_queue 565 | assert_equal 200, resp.code, "API must response with HTTP 200 status, but returned HTTP #{resp.code}" 566 | end 567 | 568 | end 569 | -------------------------------------------------------------------------------- /test/tmp.rb: -------------------------------------------------------------------------------- 1 | gem 'test-unit' 2 | require 'test/unit' 3 | require 'yaml' 4 | require 'parallel' 5 | require_relative 'test_base' 6 | 7 | class TmpTests < TestBase 8 | def setup 9 | super 10 | 11 | end 12 | 13 | 14 | 15 | end 16 | 17 | --------------------------------------------------------------------------------