@@ -34,7 +34,8 @@ groups() ->
34
34
delete_queue_subscribe ,
35
35
temp_destination_queue ,
36
36
temp_destination_in_send ,
37
- blank_destination_in_send
37
+ blank_destination_in_send ,
38
+ stream_filtering
38
39
],
39
40
40
41
[{version_to_group_name (V ), [sequence ], Tests }
@@ -102,6 +103,13 @@ init_per_testcase0(publish_unauthorized_error, Config) ->
102
103
StompPort = rabbit_ct_broker_helpers :get_node_config (Config , 0 , tcp_port_stomp ),
103
104
{ok , ClientFoo } = rabbit_stomp_client :connect (Version , " user" , " pass" , StompPort ),
104
105
rabbit_ct_helpers :set_config (Config , [{client_foo , ClientFoo }]);
106
+ init_per_testcase0 (stream_filtering , Config ) ->
107
+ case rabbit_ct_helpers :is_mixed_versions () of
108
+ true ->
109
+ {skip , " mixed version clusters are not supported for stream filtering" };
110
+ _ ->
111
+ Config
112
+ end ;
105
113
init_per_testcase0 (_ , Config ) ->
106
114
Config .
107
115
@@ -310,6 +318,162 @@ blank_destination_in_send(Config) ->
310
318
" Invalid destination" = proplists :get_value (" message" , Hdrs ),
311
319
ok .
312
320
321
+ stream_filtering (Config ) ->
322
+ Version = ? config (version , Config ),
323
+ Client = ? config (stomp_client , Config ),
324
+ Stream = atom_to_list (? FUNCTION_NAME ) ++ " -" ++ integer_to_list (rand :uniform (10000 )),
325
+ % % subscription just to create the stream from STOMP
326
+ SubDestination = " /topic/stream-queue-test" ,
327
+ rabbit_stomp_client :send (
328
+ Client , " SUBSCRIBE" ,
329
+ [{" destination" , SubDestination },
330
+ {" receipt" , " foo" },
331
+ {" x-queue-name" , Stream },
332
+ {" x-queue-type" , " stream" },
333
+ {? HEADER_X_STREAM_FILTER_SIZE_BYTES , " 32" },
334
+ {" durable" , " true" },
335
+ {" auto-delete" , " false" },
336
+ {" id" , " 1234" },
337
+ {" prefetch-count" , " 1" },
338
+ {" ack" , " client" }]),
339
+ {ok , Client1 , _ , _ } = stomp_receive (Client , " RECEIPT" ),
340
+ rabbit_stomp_client :send (
341
+ Client1 , " UNSUBSCRIBE" , [{" destination" , SubDestination },
342
+ {" id" , " 1234" },
343
+ {" receipt" , " bar" }]),
344
+ {ok , Client2 , _ , _ } = stomp_receive (Client1 , " RECEIPT" ),
345
+
346
+ % % we are going to publish several waves of messages with and without filter values.
347
+ % % we will then create subscriptions with various filter options
348
+ % % and make sure we receive only what we asked for and not all the messages.
349
+
350
+ StreamDestination = " /amq/queue/" ++ Stream ,
351
+ % % logic to publish a wave of messages with or without a filter value
352
+ WaveCount = 1000 ,
353
+ Publish =
354
+ fun (C , FilterValue ) ->
355
+ lists :foldl (fun (Seq , C0 ) ->
356
+ Headers0 = [{" destination" , StreamDestination },
357
+ {" receipt" , integer_to_list (Seq )}],
358
+ Headers = case FilterValue of
359
+ undefined ->
360
+ Headers0 ;
361
+ _ ->
362
+ [{" x-stream-filter-value" , FilterValue }] ++ Headers0
363
+ end ,
364
+ rabbit_stomp_client :send (
365
+ C0 , " SEND" , Headers , [" hello" ]),
366
+ {ok , C1 , _ , _ } = stomp_receive (C0 , " RECEIPT" ),
367
+ C1
368
+ end , C , lists :seq (1 , WaveCount ))
369
+ end ,
370
+ % % publishing messages with the "apple" filter value
371
+ Client3 = Publish (Client2 , " apple" ),
372
+ % % publishing messages with no filter value
373
+ Client4 = Publish (Client3 , undefined ),
374
+ % % publishing messages with the "orange" filter value
375
+ Client5 = Publish (Client4 , " orange" ),
376
+
377
+ % % filtering on "apple"
378
+ rabbit_stomp_client :send (
379
+ Client5 , " SUBSCRIBE" ,
380
+ [{" destination" , StreamDestination },
381
+ {" id" , " 0" },
382
+ {" ack" , " client" },
383
+ {" prefetch-count" , " 1" },
384
+ {" x-stream-filter" , " apple" },
385
+ {" x-stream-offset" , " first" }]),
386
+ {Client6 , AppleMessages } = stomp_receive_messages (Client5 , Version ),
387
+ % % we should get less than all the waves combined
388
+ ? assert (length (AppleMessages ) < WaveCount * 3 ),
389
+ % % client-side filtering
390
+ AppleFilteredMessages =
391
+ lists :filter (fun (H ) ->
392
+ proplists :get_value (" x-stream-filter-value" , H ) =:= " apple"
393
+ end , AppleMessages ),
394
+ % % we should have only the "apple" messages
395
+ ? assert (length (AppleFilteredMessages ) =:= WaveCount ),
396
+ rabbit_stomp_client :send (
397
+ Client6 , " UNSUBSCRIBE" , [{" destination" , StreamDestination },
398
+ {" id" , " 0" },
399
+ {" receipt" , " bar" }]),
400
+ {ok , Client7 , _ , _ } = stomp_receive (Client6 , " RECEIPT" ),
401
+
402
+ % % filtering on "apple" and "orange"
403
+ rabbit_stomp_client :send (
404
+ Client7 , " SUBSCRIBE" ,
405
+ [{" destination" , StreamDestination },
406
+ {" id" , " 0" },
407
+ {" ack" , " client" },
408
+ {" prefetch-count" , " 1" },
409
+ {" x-stream-filter" , " apple,orange" },
410
+ {" x-stream-offset" , " first" }]),
411
+ {Client8 , AppleOrangeMessages } = stomp_receive_messages (Client7 , Version ),
412
+ % % we should get less than all the waves combined
413
+ ? assert (length (AppleOrangeMessages ) < WaveCount * 3 ),
414
+ % % client-side filtering
415
+ AppleOrangeFilteredMessages =
416
+ lists :filter (fun (H ) ->
417
+ proplists :get_value (" x-stream-filter-value" , H ) =:= " apple" orelse
418
+ proplists :get_value (" x-stream-filter-value" , H ) =:= " orange"
419
+ end , AppleOrangeMessages ),
420
+ % % we should have only the "apple" and "orange" messages
421
+ ? assert (length (AppleOrangeFilteredMessages ) =:= WaveCount * 2 ),
422
+ rabbit_stomp_client :send (
423
+ Client8 , " UNSUBSCRIBE" , [{" destination" , StreamDestination },
424
+ {" id" , " 0" },
425
+ {" receipt" , " bar" }]),
426
+ {ok , Client9 , _ , _ } = stomp_receive (Client8 , " RECEIPT" ),
427
+
428
+ % % filtering on "apple" and messages without a filter value
429
+ rabbit_stomp_client :send (
430
+ Client9 , " SUBSCRIBE" ,
431
+ [{" destination" , StreamDestination },
432
+ {" id" , " 0" },
433
+ {" ack" , " client" },
434
+ {" prefetch-count" , " 1" },
435
+ {" x-stream-filter" , " apple" },
436
+ {" x-stream-match-unfiltered" , " true" },
437
+ {" x-stream-offset" , " first" }]),
438
+ {Client10 , AppleUnfilteredMessages } = stomp_receive_messages (Client9 , Version ),
439
+ % % we should get less than all the waves combined
440
+ ? assert (length (AppleUnfilteredMessages ) < WaveCount * 3 ),
441
+ % % client-side filtering
442
+ AppleUnfilteredFilteredMessages =
443
+ lists :filter (fun (H ) ->
444
+ proplists :get_value (" x-stream-filter-value" , H ) =:= " apple" orelse
445
+ proplists :get_value (" x-stream-filter-value" , H ) =:= undefined
446
+ end , AppleUnfilteredMessages ),
447
+ % % we should have only the "apple" messages and messages without a filter value
448
+ ? assert (length (AppleUnfilteredFilteredMessages ) =:= WaveCount * 2 ),
449
+ rabbit_stomp_client :send (
450
+ Client10 , " UNSUBSCRIBE" , [{" destination" , StreamDestination },
451
+ {" id" , " 0" },
452
+ {" receipt" , " bar" }]),
453
+ {ok , _ , _ , _ } = stomp_receive (Client10 , " RECEIPT" ),
454
+
455
+ Channel = ? config (amqp_channel , Config ),
456
+ # 'queue.delete_ok' {} = amqp_channel :call (Channel ,
457
+ # 'queue.delete' {queue = list_to_binary (Stream )}),
458
+ ok .
459
+
460
+ stomp_receive_messages (Client , Version ) ->
461
+ stomp_receive_messages (Client , [], Version ).
462
+
463
+ stomp_receive_messages (Client , Acc , Version ) ->
464
+ try rabbit_stomp_client :recv (Client ) of
465
+ {# stomp_frame {command = " MESSAGE" ,
466
+ headers = Headers }, Client1 } ->
467
+ MsgHeader = rabbit_stomp_util :msg_header_name (Version ),
468
+ AckValue = proplists :get_value (MsgHeader , Headers ),
469
+ AckHeader = rabbit_stomp_util :ack_header_name (Version ),
470
+ rabbit_stomp_client :send (Client1 , " ACK" , [{AckHeader , AckValue }]),
471
+ stomp_receive_messages (Client1 , [Headers ] ++ Acc , Version )
472
+ catch
473
+ error :{badmatch , {error , timeout }} ->
474
+ {Client , Acc }
475
+ end .
476
+
313
477
stomp_receive (Client , Command ) ->
314
478
{# stomp_frame {command = Command ,
315
479
headers = Hdrs ,
0 commit comments