summaryrefslogtreecommitdiffstats
path: root/messagebus
diff options
context:
space:
mode:
Diffstat (limited to 'messagebus')
-rw-r--r--messagebus/.gitignore7
-rw-r--r--messagebus/CMakeLists.txt26
-rw-r--r--messagebus/OWNERS2
-rw-r--r--messagebus/README1
-rw-r--r--messagebus/pom.xml98
-rw-r--r--messagebus/src/.gitignore5
-rw-r--r--messagebus/src/Doxyfile1257
-rw-r--r--messagebus/src/apps/printversion/.gitignore4
-rw-r--r--messagebus/src/apps/printversion/CMakeLists.txt8
-rw-r--r--messagebus/src/apps/printversion/printversion.cpp19
-rw-r--r--messagebus/src/binref/.gitignore3
-rw-r--r--messagebus/src/binref/CMakeLists.txt1
-rw-r--r--messagebus/src/main/config/messagebus.def30
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/AllPassThrottlePolicy.java22
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/CallStack.java83
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/ConfigAgent.java114
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/ConfigHandler.java22
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/DestinationSession.java143
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/DestinationSessionParams.java99
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/DynamicThrottlePolicy.java256
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/EmptyReply.java34
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/Error.java86
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/ErrorCode.java129
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/IntermediateSession.java141
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/IntermediateSessionParams.java122
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/Message.java244
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/MessageBus.java595
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/MessageBusParams.java146
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/MessageHandler.java19
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/Messenger.java304
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/Protocol.java54
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/ProtocolRepository.java109
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/RPCMessageBus.java112
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/RateThrottlingPolicy.java61
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/Reply.java141
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/ReplyHandler.java20
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/Result.java69
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/Routable.java126
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/SendProxy.java93
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/Sequencer.java155
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/SourceSession.java300
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/SourceSessionParams.java104
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/StaticThrottlePolicy.java91
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/ThrottlePolicy.java36
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/Trace.java158
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/TraceLevel.java30
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/TraceNode.java473
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/AverageMetric.java52
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/CountMetric.java18
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/MessageBusMetricSet.java42
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/Metric.java48
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/MetricSet.java49
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/NumberMetric.java40
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/RouteMetricSet.java45
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/ValueMetric.java14
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/metrics/package-info.java5
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java76
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/Network.java100
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/NetworkOwner.java44
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/ServiceAddress.java14
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalNetwork.java198
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalServiceAddress.java26
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalWire.java59
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/package-info.java8
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSClient.java171
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSManager.java169
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java530
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java210
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendAdapter.java31
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendV1.java321
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java71
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java116
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java84
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTarget.java173
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTargetPool.java131
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/SlobrokConfigSubscriber.java58
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/rpc/package-info.java8
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSServer.java81
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSState.java26
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/SlobrokState.java26
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/TestServer.java211
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/package-info.java6
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/package-info.java11
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/ApplicationSpec.java131
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/ErrorDirective.java63
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/Hop.java291
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/HopBlueprint.java144
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/HopDirective.java26
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/HopSpec.java353
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/PolicyDirective.java83
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/Resender.java143
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RetryPolicy.java30
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RetryTransientErrorsPolicy.java48
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/Route.java206
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/RouteDirective.java63
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RouteParser.java118
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RouteSpec.java219
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/RoutingContext.java383
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java802
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNodeIterator.java107
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingPolicy.java48
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingSpec.java225
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java264
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTableSpec.java391
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/TcpDirective.java100
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/VerbatimDirective.java65
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/package-info.java8
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicy.java57
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicyFactory.java52
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/test/QueueAdapter.java56
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/test/Receptor.java51
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/test/SimpleMessage.java40
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/test/SimpleProtocol.java100
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/test/SimpleReply.java33
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/test/package-info.java6
-rw-r--r--messagebus/src/test/files/.gitignore1
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/ChokeTestCase.java169
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/ConfigAgentTestCase.java199
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/CustomTimer.java17
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/ErrorTestCase.java92
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/MessageBusTestCase.java144
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/MessengerTestCase.java124
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/ProtocolRepositoryTestCase.java103
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/RateThrottlingTestCase.java39
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/RoutableTestCase.java114
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/SendProxyTestCase.java169
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/SequencerTestCase.java179
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/SimpleTripTestCase.java53
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/ThrottlerTestCase.java230
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/TimeoutTestCase.java102
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/TraceTestCase.java286
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/TraceTripTestCase.java116
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/IdentityTestCase.java28
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/local/LocalNetworkTest.java132
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/rpc/BasicNetworkTestCase.java152
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/rpc/LoadBalanceTestCase.java96
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/network/rpc/OOSTestCase.java200
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/rpc/RPCNetworkTestCase.java100
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/network/rpc/SendAdapterTestCase.java157
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java90
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServicePoolTestCase.java57
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/rpc/SlobrokTestCase.java151
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/network/rpc/TargetPoolTestCase.java112
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/routing/AdvancedRoutingTestCase.java119
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/routing/ResenderTestCase.java200
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/routing/RetryPolicyTestCase.java32
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/routing/RouteParserTestCase.java168
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/routing/RoutingContextTestCase.java258
-rwxr-xr-xmessagebus/src/test/java/com/yahoo/messagebus/routing/RoutingSpecTestCase.java336
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingTestCase.java1144
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/test/QueueAdapterTestCase.java108
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/test/ReceptorTestCase.java143
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/test/SimpleMessageTestCase.java25
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/test/SimpleProtocolTestCase.java65
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/test/SimpleReplyTestCase.java22
-rw-r--r--messagebus/src/testlist.txt41
-rw-r--r--messagebus/src/testrun/.gitignore9
-rw-r--r--messagebus/src/tests/.gitignore4
-rw-r--r--messagebus/src/tests/CMakeLists.txt41
-rw-r--r--messagebus/src/tests/advancedrouting/.gitignore4
-rw-r--r--messagebus/src/tests/advancedrouting/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/advancedrouting/DESC1
-rw-r--r--messagebus/src/tests/advancedrouting/FILES1
-rw-r--r--messagebus/src/tests/advancedrouting/advancedrouting.cpp188
-rw-r--r--messagebus/src/tests/auto-reply/.gitignore4
-rw-r--r--messagebus/src/tests/auto-reply/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/auto-reply/DESC2
-rw-r--r--messagebus/src/tests/auto-reply/FILES1
-rw-r--r--messagebus/src/tests/auto-reply/auto-reply.cpp40
-rw-r--r--messagebus/src/tests/blob/.gitignore4
-rw-r--r--messagebus/src/tests/blob/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/blob/DESC1
-rw-r--r--messagebus/src/tests/blob/FILES1
-rw-r--r--messagebus/src/tests/blob/blob.cpp79
-rw-r--r--messagebus/src/tests/bucketsequence/.gitignore3
-rw-r--r--messagebus/src/tests/bucketsequence/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/bucketsequence/DESC1
-rw-r--r--messagebus/src/tests/bucketsequence/FILES1
-rw-r--r--messagebus/src/tests/bucketsequence/bucketsequence.cpp50
-rw-r--r--messagebus/src/tests/choke/.gitignore4
-rw-r--r--messagebus/src/tests/choke/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/choke/DESC1
-rw-r--r--messagebus/src/tests/choke/FILES1
-rw-r--r--messagebus/src/tests/choke/choke.cpp227
-rw-r--r--messagebus/src/tests/configagent/.gitignore5
-rw-r--r--messagebus/src/tests/configagent/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/configagent/DESC2
-rw-r--r--messagebus/src/tests/configagent/FILES3
-rw-r--r--messagebus/src/tests/configagent/configagent.cpp122
-rw-r--r--messagebus/src/tests/configagent/full.cfg44
-rw-r--r--messagebus/src/tests/configagent/half.cfg23
-rw-r--r--messagebus/src/tests/context/.gitignore4
-rw-r--r--messagebus/src/tests/context/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/context/DESC1
-rw-r--r--messagebus/src/tests/context/FILES1
-rw-r--r--messagebus/src/tests/context/context.cpp102
-rwxr-xr-xmessagebus/src/tests/create-test.sh74
-rw-r--r--messagebus/src/tests/emptyreply/.gitignore4
-rw-r--r--messagebus/src/tests/emptyreply/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/emptyreply/DESC1
-rw-r--r--messagebus/src/tests/emptyreply/FILES1
-rw-r--r--messagebus/src/tests/emptyreply/emptyreply.cpp21
-rw-r--r--messagebus/src/tests/error/.gitignore4
-rw-r--r--messagebus/src/tests/error/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/error/DESC1
-rw-r--r--messagebus/src/tests/error/FILES1
-rw-r--r--messagebus/src/tests/error/error.cpp83
-rw-r--r--messagebus/src/tests/identity/.gitignore4
-rw-r--r--messagebus/src/tests/identity/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/identity/DESC1
-rw-r--r--messagebus/src/tests/identity/FILES2
-rw-r--r--messagebus/src/tests/identity/identity.cpp43
-rw-r--r--messagebus/src/tests/loadbalance/.gitignore4
-rw-r--r--messagebus/src/tests/loadbalance/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/loadbalance/DESC2
-rw-r--r--messagebus/src/tests/loadbalance/FILES1
-rw-r--r--messagebus/src/tests/loadbalance/loadbalance.cpp90
-rw-r--r--messagebus/src/tests/messagebus/.gitignore4
-rw-r--r--messagebus/src/tests/messagebus/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/messagebus/DESC1
-rw-r--r--messagebus/src/tests/messagebus/FILES1
-rw-r--r--messagebus/src/tests/messagebus/messagebus.cpp538
-rw-r--r--messagebus/src/tests/messageordering/.gitignore3
-rw-r--r--messagebus/src/tests/messageordering/CMakeLists.txt12
-rw-r--r--messagebus/src/tests/messageordering/DESC1
-rw-r--r--messagebus/src/tests/messageordering/FILES1
-rw-r--r--messagebus/src/tests/messageordering/messageordering.cpp178
-rw-r--r--messagebus/src/tests/messenger/.gitignore3
-rw-r--r--messagebus/src/tests/messenger/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/messenger/DESC1
-rw-r--r--messagebus/src/tests/messenger/FILES1
-rw-r--r--messagebus/src/tests/messenger/messenger.cpp61
-rw-r--r--messagebus/src/tests/oos/.gitignore4
-rw-r--r--messagebus/src/tests/oos/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/oos/DESC1
-rw-r--r--messagebus/src/tests/oos/FILES1
-rw-r--r--messagebus/src/tests/oos/oos.cpp231
-rw-r--r--messagebus/src/tests/oospolicy/.gitignore3
-rw-r--r--messagebus/src/tests/protocolrepository/.gitignore3
-rw-r--r--messagebus/src/tests/protocolrepository/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/protocolrepository/DESC1
-rw-r--r--messagebus/src/tests/protocolrepository/FILES1
-rw-r--r--messagebus/src/tests/protocolrepository/protocolrepository.cpp75
-rw-r--r--messagebus/src/tests/queue/.gitignore4
-rw-r--r--messagebus/src/tests/queue/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/queue/DESC1
-rw-r--r--messagebus/src/tests/queue/FILES1
-rw-r--r--messagebus/src/tests/queue/queue.cpp90
-rw-r--r--messagebus/src/tests/replygate/.gitignore4
-rw-r--r--messagebus/src/tests/replygate/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/replygate/DESC1
-rw-r--r--messagebus/src/tests/replygate/FILES1
-rw-r--r--messagebus/src/tests/replygate/replygate.cpp91
-rw-r--r--messagebus/src/tests/replyset/.gitignore3
-rw-r--r--messagebus/src/tests/resender/.gitignore4
-rw-r--r--messagebus/src/tests/resender/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/resender/DESC1
-rw-r--r--messagebus/src/tests/resender/FILES1
-rw-r--r--messagebus/src/tests/resender/resender.cpp310
-rw-r--r--messagebus/src/tests/result/.gitignore4
-rw-r--r--messagebus/src/tests/result/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/result/DESC1
-rw-r--r--messagebus/src/tests/result/FILES1
-rw-r--r--messagebus/src/tests/result/result.cpp76
-rw-r--r--messagebus/src/tests/retrypolicy/.gitignore4
-rw-r--r--messagebus/src/tests/retrypolicy/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/retrypolicy/DESC1
-rw-r--r--messagebus/src/tests/retrypolicy/FILES1
-rw-r--r--messagebus/src/tests/retrypolicy/retrypolicy.cpp39
-rw-r--r--messagebus/src/tests/routable/.gitignore4
-rw-r--r--messagebus/src/tests/routable/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/routable/DESC1
-rw-r--r--messagebus/src/tests/routable/FILES1
-rw-r--r--messagebus/src/tests/routable/routable.cpp94
-rw-r--r--messagebus/src/tests/routablequeue/.gitignore4
-rw-r--r--messagebus/src/tests/routablequeue/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/routablequeue/DESC1
-rw-r--r--messagebus/src/tests/routablequeue/FILES1
-rw-r--r--messagebus/src/tests/routablequeue/routablequeue.cpp110
-rw-r--r--messagebus/src/tests/routeparser/.gitignore4
-rw-r--r--messagebus/src/tests/routeparser/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/routeparser/DESC1
-rw-r--r--messagebus/src/tests/routeparser/FILES1
-rw-r--r--messagebus/src/tests/routeparser/routeparser.cpp280
-rw-r--r--messagebus/src/tests/routing/.gitignore4
-rw-r--r--messagebus/src/tests/routing/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/routing/DESC1
-rw-r--r--messagebus/src/tests/routing/FILES1
-rw-r--r--messagebus/src/tests/routing/routing.cpp1580
-rw-r--r--messagebus/src/tests/routingcontext/.gitignore4
-rw-r--r--messagebus/src/tests/routingcontext/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/routingcontext/DESC1
-rw-r--r--messagebus/src/tests/routingcontext/FILES1
-rw-r--r--messagebus/src/tests/routingcontext/routingcontext.cpp389
-rw-r--r--messagebus/src/tests/routingspec/.gitignore4
-rw-r--r--messagebus/src/tests/routingspec/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/routingspec/DESC1
-rw-r--r--messagebus/src/tests/routingspec/FILES1
-rw-r--r--messagebus/src/tests/routingspec/routingspec.cpp251
-rw-r--r--messagebus/src/tests/rpcserviceaddress/.gitignore4
-rw-r--r--messagebus/src/tests/rpcserviceaddress/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/rpcserviceaddress/DESC1
-rw-r--r--messagebus/src/tests/rpcserviceaddress/FILES1
-rw-r--r--messagebus/src/tests/rpcserviceaddress/rpcserviceaddress.cpp44
-rw-r--r--messagebus/src/tests/selector/.gitignore3
-rw-r--r--messagebus/src/tests/sendadapter/.gitignore4
-rw-r--r--messagebus/src/tests/sendadapter/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/sendadapter/DESC1
-rw-r--r--messagebus/src/tests/sendadapter/FILES1
-rw-r--r--messagebus/src/tests/sendadapter/sendadapter.cpp252
-rw-r--r--messagebus/src/tests/sequencer/.gitignore4
-rw-r--r--messagebus/src/tests/sequencer/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/sequencer/DESC1
-rw-r--r--messagebus/src/tests/sequencer/FILES1
-rw-r--r--messagebus/src/tests/sequencer/sequencer.cpp194
-rw-r--r--messagebus/src/tests/serviceaddress/.gitignore4
-rw-r--r--messagebus/src/tests/serviceaddress/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/serviceaddress/DESC1
-rw-r--r--messagebus/src/tests/serviceaddress/FILES1
-rw-r--r--messagebus/src/tests/serviceaddress/serviceaddress.cpp137
-rw-r--r--messagebus/src/tests/servicepool/.gitignore4
-rw-r--r--messagebus/src/tests/servicepool/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/servicepool/DESC1
-rw-r--r--messagebus/src/tests/servicepool/FILES1
-rw-r--r--messagebus/src/tests/servicepool/servicepool.cpp71
-rw-r--r--messagebus/src/tests/shutdown/.gitignore4
-rw-r--r--messagebus/src/tests/shutdown/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/shutdown/DESC1
-rw-r--r--messagebus/src/tests/shutdown/FILES1
-rw-r--r--messagebus/src/tests/shutdown/shutdown.cpp159
-rw-r--r--messagebus/src/tests/simple-roundtrip/.gitignore4
-rw-r--r--messagebus/src/tests/simple-roundtrip/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/simple-roundtrip/DESC1
-rw-r--r--messagebus/src/tests/simple-roundtrip/FILES1
-rw-r--r--messagebus/src/tests/simple-roundtrip/simple-roundtrip.cpp101
-rw-r--r--messagebus/src/tests/simpleprotocol/.gitignore4
-rw-r--r--messagebus/src/tests/simpleprotocol/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/simpleprotocol/DESC3
-rw-r--r--messagebus/src/tests/simpleprotocol/FILES1
-rw-r--r--messagebus/src/tests/simpleprotocol/simpleprotocol.cpp72
-rw-r--r--messagebus/src/tests/slobrok/.gitignore4
-rw-r--r--messagebus/src/tests/slobrok/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/slobrok/DESC2
-rw-r--r--messagebus/src/tests/slobrok/FILES1
-rw-r--r--messagebus/src/tests/slobrok/slobrok.cpp134
-rw-r--r--messagebus/src/tests/sourcesession/.gitignore4
-rw-r--r--messagebus/src/tests/sourcesession/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/sourcesession/DESC3
-rw-r--r--messagebus/src/tests/sourcesession/FILES1
-rw-r--r--messagebus/src/tests/sourcesession/sourcesession.cpp339
-rw-r--r--messagebus/src/tests/targetpool/.gitignore4
-rw-r--r--messagebus/src/tests/targetpool/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/targetpool/DESC1
-rw-r--r--messagebus/src/tests/targetpool/FILES1
-rw-r--r--messagebus/src/tests/targetpool/targetpool.cpp100
-rw-r--r--messagebus/src/tests/throttling/.gitignore4
-rw-r--r--messagebus/src/tests/throttling/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/throttling/DESC1
-rw-r--r--messagebus/src/tests/throttling/FILES1
-rw-r--r--messagebus/src/tests/throttling/throttling.cpp362
-rw-r--r--messagebus/src/tests/timeout/.gitignore4
-rw-r--r--messagebus/src/tests/timeout/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/timeout/DESC1
-rw-r--r--messagebus/src/tests/timeout/FILES1
-rw-r--r--messagebus/src/tests/timeout/timeout.cpp83
-rw-r--r--messagebus/src/tests/trace-roundtrip/.gitignore4
-rw-r--r--messagebus/src/tests/trace-roundtrip/CMakeLists.txt9
-rw-r--r--messagebus/src/tests/trace-roundtrip/DESC1
-rw-r--r--messagebus/src/tests/trace-roundtrip/FILES1
-rw-r--r--messagebus/src/tests/trace-roundtrip/trace-roundtrip.cpp127
-rw-r--r--messagebus/src/vespa/messagebus/.gitignore7
-rw-r--r--messagebus/src/vespa/messagebus/CMakeLists.txt41
-rw-r--r--messagebus/src/vespa/messagebus/blob.cpp10
-rw-r--r--messagebus/src/vespa/messagebus/blob.h67
-rw-r--r--messagebus/src/vespa/messagebus/blobref.cpp10
-rw-r--r--messagebus/src/vespa/messagebus/blobref.h55
-rw-r--r--messagebus/src/vespa/messagebus/callstack.cpp53
-rw-r--r--messagebus/src/vespa/messagebus/callstack.h88
-rw-r--r--messagebus/src/vespa/messagebus/common.h13
-rw-r--r--messagebus/src/vespa/messagebus/configagent.cpp50
-rw-r--r--messagebus/src/vespa/messagebus/configagent.h32
-rw-r--r--messagebus/src/vespa/messagebus/context.h56
-rwxr-xr-xmessagebus/src/vespa/messagebus/create-class-cpp.sh27
-rwxr-xr-xmessagebus/src/vespa/messagebus/create-class-h.sh26
-rwxr-xr-xmessagebus/src/vespa/messagebus/create-interface.sh22
-rw-r--r--messagebus/src/vespa/messagebus/destinationsession.cpp60
-rw-r--r--messagebus/src/vespa/messagebus/destinationsession.h104
-rw-r--r--messagebus/src/vespa/messagebus/destinationsessionparams.cpp15
-rw-r--r--messagebus/src/vespa/messagebus/destinationsessionparams.h77
-rw-r--r--messagebus/src/vespa/messagebus/dynamicthrottlepolicy.cpp205
-rw-r--r--messagebus/src/vespa/messagebus/dynamicthrottlepolicy.h178
-rw-r--r--messagebus/src/vespa/messagebus/emptyreply.cpp39
-rw-r--r--messagebus/src/vespa/messagebus/emptyreply.h49
-rw-r--r--messagebus/src/vespa/messagebus/error.cpp39
-rw-r--r--messagebus/src/vespa/messagebus/error.h71
-rw-r--r--messagebus/src/vespa/messagebus/errorcode.cpp54
-rw-r--r--messagebus/src/vespa/messagebus/errorcode.h128
-rw-r--r--messagebus/src/vespa/messagebus/iconfighandler.h33
-rw-r--r--messagebus/src/vespa/messagebus/idiscardhandler.h31
-rw-r--r--messagebus/src/vespa/messagebus/imessagehandler.h29
-rw-r--r--messagebus/src/vespa/messagebus/intermediatesession.cpp80
-rw-r--r--messagebus/src/vespa/messagebus/intermediatesession.h100
-rw-r--r--messagebus/src/vespa/messagebus/intermediatesessionparams.cpp16
-rw-r--r--messagebus/src/vespa/messagebus/intermediatesessionparams.h95
-rw-r--r--messagebus/src/vespa/messagebus/iprotocol.h83
-rw-r--r--messagebus/src/vespa/messagebus/ireplyhandler.h28
-rw-r--r--messagebus/src/vespa/messagebus/ithrottlepolicy.h53
-rw-r--r--messagebus/src/vespa/messagebus/itimer.h36
-rw-r--r--messagebus/src/vespa/messagebus/message.cpp77
-rw-r--r--messagebus/src/vespa/messagebus/message.h229
-rw-r--r--messagebus/src/vespa/messagebus/messagebus.cpp424
-rw-r--r--messagebus/src/vespa/messagebus/messagebus.h308
-rw-r--r--messagebus/src/vespa/messagebus/messagebusparams.cpp36
-rw-r--r--messagebus/src/vespa/messagebus/messagebusparams.h103
-rw-r--r--messagebus/src/vespa/messagebus/messenger.cpp286
-rw-r--r--messagebus/src/vespa/messagebus/messenger.h128
-rw-r--r--messagebus/src/vespa/messagebus/network/.gitignore2
-rw-r--r--messagebus/src/vespa/messagebus/network/CMakeLists.txt16
-rw-r--r--messagebus/src/vespa/messagebus/network/identity.cpp32
-rw-r--r--messagebus/src/vespa/messagebus/network/identity.h58
-rw-r--r--messagebus/src/vespa/messagebus/network/inetwork.h145
-rw-r--r--messagebus/src/vespa/messagebus/network/inetworkowner.h49
-rw-r--r--messagebus/src/vespa/messagebus/network/iserviceaddress.h27
-rw-r--r--messagebus/src/vespa/messagebus/network/oosclient.cpp111
-rw-r--r--messagebus/src/vespa/messagebus/network/oosclient.h125
-rw-r--r--messagebus/src/vespa/messagebus/network/oosmanager.cpp105
-rw-r--r--messagebus/src/vespa/messagebus/network/oosmanager.h93
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcnetwork.cpp381
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcnetwork.h253
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp25
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcnetworkparams.h186
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcsendadapter.h57
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcsendv1.cpp410
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcsendv1.h83
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcservice.cpp54
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcservice.h59
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp52
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcserviceaddress.h90
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcservicepool.cpp49
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcservicepool.h66
-rw-r--r--messagebus/src/vespa/messagebus/network/rpctarget.cpp100
-rw-r--r--messagebus/src/vespa/messagebus/network/rpctarget.h120
-rw-r--r--messagebus/src/vespa/messagebus/network/rpctargetpool.cpp92
-rw-r--r--messagebus/src/vespa/messagebus/network/rpctargetpool.h97
-rw-r--r--messagebus/src/vespa/messagebus/protocolrepository.cpp82
-rw-r--r--messagebus/src/vespa/messagebus/protocolrepository.h76
-rw-r--r--messagebus/src/vespa/messagebus/protocolset.cpp39
-rw-r--r--messagebus/src/vespa/messagebus/protocolset.h52
-rw-r--r--messagebus/src/vespa/messagebus/queue.h64
-rw-r--r--messagebus/src/vespa/messagebus/reply.cpp87
-rw-r--r--messagebus/src/vespa/messagebus/reply.h129
-rw-r--r--messagebus/src/vespa/messagebus/replygate.cpp51
-rw-r--r--messagebus/src/vespa/messagebus/replygate.h69
-rw-r--r--messagebus/src/vespa/messagebus/result.cpp86
-rw-r--r--messagebus/src/vespa/messagebus/result.h122
-rw-r--r--messagebus/src/vespa/messagebus/routable.cpp35
-rw-r--r--messagebus/src/vespa/messagebus/routable.h175
-rw-r--r--messagebus/src/vespa/messagebus/routablequeue.cpp79
-rw-r--r--messagebus/src/vespa/messagebus/routablequeue.h90
-rw-r--r--messagebus/src/vespa/messagebus/routing/.gitignore5
-rw-r--r--messagebus/src/vespa/messagebus/routing/CMakeLists.txt24
-rw-r--r--messagebus/src/vespa/messagebus/routing/errordirective.cpp26
-rw-r--r--messagebus/src/vespa/messagebus/routing/errordirective.h41
-rw-r--r--messagebus/src/vespa/messagebus/routing/hop.cpp148
-rw-r--r--messagebus/src/vespa/messagebus/routing/hop.h180
-rw-r--r--messagebus/src/vespa/messagebus/routing/hopblueprint.cpp65
-rw-r--r--messagebus/src/vespa/messagebus/routing/hopblueprint.h103
-rw-r--r--messagebus/src/vespa/messagebus/routing/hopspec.cpp89
-rw-r--r--messagebus/src/vespa/messagebus/routing/hopspec.h159
-rw-r--r--messagebus/src/vespa/messagebus/routing/ihopdirective.h71
-rw-r--r--messagebus/src/vespa/messagebus/routing/iretrypolicy.h49
-rw-r--r--messagebus/src/vespa/messagebus/routing/iroutingpolicy.h53
-rw-r--r--messagebus/src/vespa/messagebus/routing/policydirective.cpp31
-rw-r--r--messagebus/src/vespa/messagebus/routing/policydirective.h51
-rw-r--r--messagebus/src/vespa/messagebus/routing/resender.cpp99
-rw-r--r--messagebus/src/vespa/messagebus/routing/resender.h92
-rw-r--r--messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.cpp42
-rw-r--r--messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.h53
-rw-r--r--messagebus/src/vespa/messagebus/routing/route.cpp83
-rw-r--r--messagebus/src/vespa/messagebus/routing/route.h128
-rw-r--r--messagebus/src/vespa/messagebus/routing/routedirective.cpp36
-rw-r--r--messagebus/src/vespa/messagebus/routing/routedirective.h42
-rw-r--r--messagebus/src/vespa/messagebus/routing/routeparser.cpp153
-rw-r--r--messagebus/src/vespa/messagebus/routing/routeparser.h44
-rw-r--r--messagebus/src/vespa/messagebus/routing/routespec.cpp72
-rw-r--r--messagebus/src/vespa/messagebus/routing/routespec.h134
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingcontext.cpp239
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingcontext.h291
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingnode.cpp601
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingnode.h446
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingnodeiterator.cpp64
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingnodeiterator.h80
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingspec.cpp81
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingspec.h136
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingtable.cpp77
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingtable.h152
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingtablespec.cpp89
-rw-r--r--messagebus/src/vespa/messagebus/routing/routingtablespec.h192
-rw-r--r--messagebus/src/vespa/messagebus/routing/tcpdirective.cpp43
-rw-r--r--messagebus/src/vespa/messagebus/routing/tcpdirective.h60
-rw-r--r--messagebus/src/vespa/messagebus/routing/verbatimdirective.cpp36
-rw-r--r--messagebus/src/vespa/messagebus/routing/verbatimdirective.h41
-rw-r--r--messagebus/src/vespa/messagebus/rpcmessagebus.cpp38
-rw-r--r--messagebus/src/vespa/messagebus/rpcmessagebus.h99
-rw-r--r--messagebus/src/vespa/messagebus/sendproxy.cpp72
-rw-r--r--messagebus/src/vespa/messagebus/sendproxy.h50
-rw-r--r--messagebus/src/vespa/messagebus/sequencer.cpp111
-rw-r--r--messagebus/src/vespa/messagebus/sequencer.h89
-rw-r--r--messagebus/src/vespa/messagebus/sourcesession.cpp166
-rw-r--r--messagebus/src/vespa/messagebus/sourcesession.h128
-rw-r--r--messagebus/src/vespa/messagebus/sourcesessionparams.cpp65
-rw-r--r--messagebus/src/vespa/messagebus/sourcesessionparams.h83
-rw-r--r--messagebus/src/vespa/messagebus/staticthrottlepolicy.cpp75
-rw-r--r--messagebus/src/vespa/messagebus/staticthrottlepolicy.h84
-rw-r--r--messagebus/src/vespa/messagebus/systemtimer.cpp15
-rw-r--r--messagebus/src/vespa/messagebus/systemtimer.h20
-rw-r--r--messagebus/src/vespa/messagebus/testlib/.gitignore3
-rw-r--r--messagebus/src/vespa/messagebus/testlib/CMakeLists.txt17
-rwxr-xr-xmessagebus/src/vespa/messagebus/testlib/create-class-cpp.sh27
-rwxr-xr-xmessagebus/src/vespa/messagebus/testlib/create-class-h.sh26
-rwxr-xr-xmessagebus/src/vespa/messagebus/testlib/create-interface.sh22
-rw-r--r--messagebus/src/vespa/messagebus/testlib/custompolicy.cpp143
-rw-r--r--messagebus/src/vespa/messagebus/testlib/custompolicy.h41
-rw-r--r--messagebus/src/vespa/messagebus/testlib/oosserver.cpp83
-rw-r--r--messagebus/src/vespa/messagebus/testlib/oosserver.h39
-rw-r--r--messagebus/src/vespa/messagebus/testlib/oosstate.cpp34
-rw-r--r--messagebus/src/vespa/messagebus/testlib/oosstate.h27
-rw-r--r--messagebus/src/vespa/messagebus/testlib/receptor.cpp67
-rw-r--r--messagebus/src/vespa/messagebus/testlib/receptor.h30
-rw-r--r--messagebus/src/vespa/messagebus/testlib/simplemessage.cpp87
-rw-r--r--messagebus/src/vespa/messagebus/testlib/simplemessage.h35
-rw-r--r--messagebus/src/vespa/messagebus/testlib/simpleprotocol.cpp155
-rw-r--r--messagebus/src/vespa/messagebus/testlib/simpleprotocol.h91
-rw-r--r--messagebus/src/vespa/messagebus/testlib/simplereply.cpp47
-rw-r--r--messagebus/src/vespa/messagebus/testlib/simplereply.h29
-rw-r--r--messagebus/src/vespa/messagebus/testlib/slobrok.cpp107
-rw-r--r--messagebus/src/vespa/messagebus/testlib/slobrok.h45
-rw-r--r--messagebus/src/vespa/messagebus/testlib/slobrokstate.cpp34
-rw-r--r--messagebus/src/vespa/messagebus/testlib/slobrokstate.h27
-rw-r--r--messagebus/src/vespa/messagebus/testlib/testserver.cpp106
-rw-r--r--messagebus/src/vespa/messagebus/testlib/testserver.h53
-rw-r--r--messagebus/src/vespa/messagebus/trace.h18
-rw-r--r--messagebus/src/vespa/messagebus/tracelevel.h10
-rw-r--r--messagebus/src/vespa/messagebus/tracenode.h12
-rw-r--r--messagebus/src/vespa/messagebus/vtag.cpp81
-rw-r--r--messagebus/src/vespa/messagebus/vtag.h23
-rw-r--r--messagebus/test/CMakeLists.txt10
-rw-r--r--messagebus/test/src/.gitignore8
-rw-r--r--messagebus/test/src/binref/.gitignore5
-rw-r--r--messagebus/test/src/binref/CMakeLists.txt5
-rwxr-xr-xmessagebus/test/src/binref/compilejava.in11
-rw-r--r--messagebus/test/src/binref/env.sh.in2
l---------messagebus/test/src/binref/progctl.sh1
-rwxr-xr-xmessagebus/test/src/binref/runjava.in13
l---------messagebus/test/src/binref/sbcmd1
l---------messagebus/test/src/binref/slobrok1
l---------messagebus/test/src/binref/testrun.sh1
-rwxr-xr-xmessagebus/test/src/setup.sh11
-rw-r--r--messagebus/test/src/test-report-index.html17
-rw-r--r--messagebus/test/src/testlist.txt6
-rw-r--r--messagebus/test/src/tests/compile-cpp/.gitignore4
-rw-r--r--messagebus/test/src/tests/compile-cpp/CMakeLists.txt7
-rw-r--r--messagebus/test/src/tests/compile-cpp/DESC2
-rw-r--r--messagebus/test/src/tests/compile-cpp/FILES1
-rw-r--r--messagebus/test/src/tests/compile-cpp/compile-cpp.cpp16
-rw-r--r--messagebus/test/src/tests/compile-java/.gitignore4
-rw-r--r--messagebus/test/src/tests/compile-java/CMakeLists.txt2
-rw-r--r--messagebus/test/src/tests/compile-java/DESC2
-rw-r--r--messagebus/test/src/tests/compile-java/FILES1
-rw-r--r--messagebus/test/src/tests/compile-java/TestCompile.java9
-rwxr-xr-xmessagebus/test/src/tests/compile-java/compile-java_test.sh6
-rwxr-xr-xmessagebus/test/src/tests/create-test.sh71
-rw-r--r--messagebus/test/src/tests/error/.gitignore15
-rw-r--r--messagebus/test/src/tests/error/CMakeLists.txt17
-rw-r--r--messagebus/test/src/tests/error/DESC2
-rw-r--r--messagebus/test/src/tests/error/FILES8
-rw-r--r--messagebus/test/src/tests/error/JavaClient.java65
-rw-r--r--messagebus/test/src/tests/error/JavaServer.java47
-rw-r--r--messagebus/test/src/tests/error/cpp-client.cpp75
-rw-r--r--messagebus/test/src/tests/error/cpp-server.cpp73
-rwxr-xr-xmessagebus/test/src/tests/error/ctl.sh3
-rw-r--r--messagebus/test/src/tests/error/error.cpp45
-rwxr-xr-xmessagebus/test/src/tests/error/error_test.sh6
-rw-r--r--messagebus/test/src/tests/error/progdefs.sh3
-rw-r--r--messagebus/test/src/tests/error/routing-template.cfg11
-rw-r--r--messagebus/test/src/tests/errorcodes/.gitignore7
-rw-r--r--messagebus/test/src/tests/errorcodes/CMakeLists.txt7
-rw-r--r--messagebus/test/src/tests/errorcodes/DESC2
-rw-r--r--messagebus/test/src/tests/errorcodes/DumpCodes.java51
-rw-r--r--messagebus/test/src/tests/errorcodes/FILES5
-rw-r--r--messagebus/test/src/tests/errorcodes/dumpcodes.cpp70
-rw-r--r--messagebus/test/src/tests/errorcodes/errorcodes_test.sh9
-rw-r--r--messagebus/test/src/tests/errorcodes/ref-dump.txt34
-rw-r--r--messagebus/test/src/tests/speed/.gitignore15
-rw-r--r--messagebus/test/src/tests/speed/CMakeLists.txt17
-rw-r--r--messagebus/test/src/tests/speed/DESC4
-rw-r--r--messagebus/test/src/tests/speed/FILES8
-rw-r--r--messagebus/test/src/tests/speed/JavaClient.java137
-rw-r--r--messagebus/test/src/tests/speed/JavaServer.java54
-rw-r--r--messagebus/test/src/tests/speed/cpp-client.cpp146
-rw-r--r--messagebus/test/src/tests/speed/cpp-server.cpp77
-rwxr-xr-xmessagebus/test/src/tests/speed/ctl.sh3
-rw-r--r--messagebus/test/src/tests/speed/progdefs.sh3
-rw-r--r--messagebus/test/src/tests/speed/routing-template.cfg11
-rw-r--r--messagebus/test/src/tests/speed/speed.cpp51
-rw-r--r--messagebus/test/src/tests/speed/speed_test.sh8
-rw-r--r--messagebus/test/src/tests/trace/.gitignore12
-rw-r--r--messagebus/test/src/tests/trace/CMakeLists.txt12
-rw-r--r--messagebus/test/src/tests/trace/DESC1
-rw-r--r--messagebus/test/src/tests/trace/FILES19
-rw-r--r--messagebus/test/src/tests/trace/JavaServer.java97
-rw-r--r--messagebus/test/src/tests/trace/cpp-server.cpp90
-rwxr-xr-xmessagebus/test/src/tests/trace/ctl.sh3
-rw-r--r--messagebus/test/src/tests/trace/progdefs.sh15
-rw-r--r--messagebus/test/src/tests/trace/trace.cpp113
-rw-r--r--messagebus/test/src/tests/trace/trace_test.sh7
-rw-r--r--messagebus/test/testrun/.gitignore9
617 files changed, 45569 insertions, 0 deletions
diff --git a/messagebus/.gitignore b/messagebus/.gitignore
new file mode 100644
index 00000000000..b79b059b0a7
--- /dev/null
+++ b/messagebus/.gitignore
@@ -0,0 +1,7 @@
+.classpath
+.project
+messagebus-lib.iml
+target
+/pom.xml.build
+Makefile
+Testing
diff --git a/messagebus/CMakeLists.txt b/messagebus/CMakeLists.txt
new file mode 100644
index 00000000000..c66874e4ec4
--- /dev/null
+++ b/messagebus/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_define_module(
+ DEPENDS
+ fastos
+ vespalog
+ config_cloudconfig
+ vespalib
+ staging_vespalib
+ fnet
+ slobrok
+ slobrok_slobrokserver
+
+ LIBS
+ src/vespa/messagebus
+ src/vespa/messagebus/network
+ src/vespa/messagebus/routing
+ src/vespa/messagebus/testlib
+
+ APPS
+ src/apps/printversion
+ src/binref
+
+ TESTS
+ src/tests
+ test
+)
diff --git a/messagebus/OWNERS b/messagebus/OWNERS
new file mode 100644
index 00000000000..631c3b2dd30
--- /dev/null
+++ b/messagebus/OWNERS
@@ -0,0 +1,2 @@
+dybdahl
+balder
diff --git a/messagebus/README b/messagebus/README
new file mode 100644
index 00000000000..55518dcab92
--- /dev/null
+++ b/messagebus/README
@@ -0,0 +1 @@
+This is the generic Vespa message bus infrastructure.
diff --git a/messagebus/pom.xml b/messagebus/pom.xml
new file mode 100644
index 00000000000..16868d49e7c
--- /dev/null
+++ b/messagebus/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>messagebus</artifactId>
+ <version>6-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespajlib</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>component</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>config</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>configdefinitions</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>jrt</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Werror</arg>
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-serial</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <!-- append to the packaging phase. -->
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>config-class-plugin</artifactId>
+ <version>${project.version}</version>
+ <configuration>
+ <defFilesDirectories>src/main/config/</defFilesDirectories>
+ </configuration>
+ <executions>
+ <execution>
+ <id>config-gen</id>
+ <goals>
+ <goal>config-gen</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/messagebus/src/.gitignore b/messagebus/src/.gitignore
new file mode 100644
index 00000000000..3b9f1ee8e62
--- /dev/null
+++ b/messagebus/src/.gitignore
@@ -0,0 +1,5 @@
+Makefile.ini
+config_command.sh
+doxygen
+messagebus.mak
+project.dsw
diff --git a/messagebus/src/Doxyfile b/messagebus/src/Doxyfile
new file mode 100644
index 00000000000..c0392277d2a
--- /dev/null
+++ b/messagebus/src/Doxyfile
@@ -0,0 +1,1257 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Doxyfile 1.4.7
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = messagebus
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = ../../doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish,
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese,
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish,
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# This tag can be used to specify the encoding used in the generated output.
+# The encoding is not always determined by the language that is chosen,
+# but also whether or not the output is meant for Windows or non-Windows users.
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
+# forces the Windows encoding (this is the default for the Windows binary),
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for Java.
+# For instance, namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to
+# include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from the
+# version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = messagebus
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py
+
+FILE_PATTERNS = *.h \
+ *.cpp
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE = messagebus/testlib \
+ messagebus/sanity.cpp \
+ messagebus/config-messagebus.h \
+ messagebus/config-messagebus.cpp
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code. Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED = IAM_DOXYGEN
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a call dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a caller dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_WIDTH = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that a graph may be further truncated if the graph's
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default),
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, which results in a white background.
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/messagebus/src/apps/printversion/.gitignore b/messagebus/src/apps/printversion/.gitignore
new file mode 100644
index 00000000000..46d3c9812ec
--- /dev/null
+++ b/messagebus/src/apps/printversion/.gitignore
@@ -0,0 +1,4 @@
+/.depend
+/Makefile
+/printversion
+messagebus_printversion_app
diff --git a/messagebus/src/apps/printversion/CMakeLists.txt b/messagebus/src/apps/printversion/CMakeLists.txt
new file mode 100644
index 00000000000..2576eef901e
--- /dev/null
+++ b/messagebus/src/apps/printversion/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_printversion_app
+ SOURCES
+ printversion.cpp
+ INSTALL bin
+ DEPENDS
+ messagebus
+)
diff --git a/messagebus/src/apps/printversion/printversion.cpp b/messagebus/src/apps/printversion/printversion.cpp
new file mode 100644
index 00000000000..8401653fc51
--- /dev/null
+++ b/messagebus/src/apps/printversion/printversion.cpp
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/messagebus/vtag.h>
+#include <stdio.h>
+#include <vespa/vespalib/component/version.h>
+
+int main(int, char **)
+{
+ printf("version tag: %s\n", mbus::VersionTag);
+ printf("version tag date: %s\n", mbus::VersionTagDate);
+ printf("version tag system: %s\n", mbus::VersionTagSystem);
+ printf("version tag system rev: %s\n", mbus::VersionTagSystemRev);
+ printf("version tag builder: %s\n", mbus::VersionTagBuilder);
+ printf("nice version:\n\t");
+ mbus::Vtag::printVersionNice();
+ printf("\n");
+ printf("currentVersion object: %s\n", mbus::Vtag::currentVersion.toString().c_str());
+ return 0;
+}
diff --git a/messagebus/src/binref/.gitignore b/messagebus/src/binref/.gitignore
new file mode 100644
index 00000000000..cfb0e619824
--- /dev/null
+++ b/messagebus/src/binref/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+testrun.sh
diff --git a/messagebus/src/binref/CMakeLists.txt b/messagebus/src/binref/CMakeLists.txt
new file mode 100644
index 00000000000..5c90dd5bfcc
--- /dev/null
+++ b/messagebus/src/binref/CMakeLists.txt
@@ -0,0 +1 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
diff --git a/messagebus/src/main/config/messagebus.def b/messagebus/src/main/config/messagebus.def
new file mode 100644
index 00000000000..c88e4ebbe6b
--- /dev/null
+++ b/messagebus/src/main/config/messagebus.def
@@ -0,0 +1,30 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=2
+namespace=messagebus
+
+# Name of the protocol that uses this routing table. All
+# instances of message bus must support all named protocols.
+routingtable[].protocol string
+
+# A protocol-unique name for a hop.
+routingtable[].hop[].name string
+
+# The selector string of a hop, this string typically contains
+# routing policy references on the form [policy-name:parameter].
+# The protocol for the routing table must support all named
+# policies.
+routingtable[].hop[].selector string
+
+# List of recipients for a hop. These strings may contain
+# wildcards to allow the network layer to choose any single
+# matching service.
+routingtable[].hop[].recipient[] string
+
+# Whether or not to ignore the result from this hop.
+routingtable[].hop[].ignoreresult bool default=false
+
+# A protocol-unique name for a route.
+routingtable[].route[].name string
+
+# An array of hop names that together make up the route.
+routingtable[].route[].hop[] string
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/AllPassThrottlePolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/AllPassThrottlePolicy.java
new file mode 100644
index 00000000000..72f13ac2a1e
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/AllPassThrottlePolicy.java
@@ -0,0 +1,22 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * This is an implementation of the {@link ThrottlePolicy} that passes all requests (no real throttling).
+ * @author <a href="mailto:dybdahl@yahoo-inc.com">Haakon Dybdahl</a>
+ */
+public class AllPassThrottlePolicy implements ThrottlePolicy
+{
+ @Override
+ public boolean canSend(Message msg, int pendingCount) {
+ return true;
+ }
+
+ @Override
+ public void processMessage(Message msg) {
+ }
+
+ @Override
+ public void processReply(Reply reply) {
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/CallStack.java b/messagebus/src/main/java/com/yahoo/messagebus/CallStack.java
new file mode 100644
index 00000000000..c4a1f2d9097
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/CallStack.java
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Stack;
+
+/**
+ * An wrapper around a stack of frame objects that is aware of the message that owns it. It contains functionality to
+ * move the content of itself to another, never to copy, since a callback is unique and might be counted by
+ * implementations such as Resender.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CallStack {
+
+ private Deque<StackFrame> stack = new ArrayDeque<>();
+
+ /**
+ * Push a handler onto the callstack of this message with a given context.
+ *
+ * @param handler The reply handler to store.
+ * @param context The context to be associated with the message for that handler.
+ */
+ public void push(ReplyHandler handler, Object context) {
+ stack.push(new StackFrame(handler, context));
+ }
+
+ /**
+ * Pop a frame from this stack. The handler part of the frame will be returned and the context part will be set on
+ * the given reply. Invoke this method on an empty stack and terrible things will happen.
+ *
+ * @param routable The routable that will have its context set.
+ * @return The next handler on the stack.
+ */
+ public ReplyHandler pop(Routable routable) {
+ StackFrame frame = stack.pop();
+ routable.setContext(frame.context);
+ return frame.handler;
+ }
+
+ /**
+ * Swap the content of this and the argument stack.
+ *
+ * @param other The stack to swap content with.
+ */
+ public void swap(CallStack other) {
+ Deque<StackFrame> tmp = stack;
+ stack = other.stack;
+ other.stack = tmp;
+ }
+
+ /**
+ * Clear this call stack. This method should only be used when you are certain that it is safe to just throw away
+ * the stack. It has similar effects to stopping a thread, you need to know where it is safe to do so.
+ */
+ public void clear() {
+ stack.clear();
+ }
+
+ /**
+ * Returns the number of elements of the callstack.
+ *
+ * @return The number of elements.
+ */
+ public int size() {
+ return stack.size();
+ }
+
+ /**
+ * Helper class that holds stack frame data.
+ */
+ private static class StackFrame {
+
+ private final ReplyHandler handler;
+ private final Object context;
+
+ public StackFrame(ReplyHandler handler, Object context) {
+ this.handler = handler;
+ this.context = context;
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/ConfigAgent.java b/messagebus/src/main/java/com/yahoo/messagebus/ConfigAgent.java
new file mode 100755
index 00000000000..386441e381c
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/ConfigAgent.java
@@ -0,0 +1,114 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.config.subscription.ConfigSubscriber;
+import com.yahoo.config.subscription.ConfigURI;
+import com.yahoo.messagebus.MessagebusConfig;
+import com.yahoo.messagebus.routing.HopSpec;
+import com.yahoo.messagebus.routing.RouteSpec;
+import com.yahoo.messagebus.routing.RoutingSpec;
+import com.yahoo.messagebus.routing.RoutingTableSpec;
+
+/**
+ * This class implements subscription to message bus config. To use configuration one must implement the {@link
+ * ConfigHandler} interface.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ConfigAgent implements ConfigSubscriber.SingleSubscriber<MessagebusConfig>{
+ private final ConfigURI configURI;
+ private final ConfigHandler handler;
+ private ConfigSubscriber subscriber;
+
+ /**
+ * Create a config agent that will obtain config for the given handler and configure it programmatically.
+ *
+ * @param configId the config id we want to use
+ * @param handler the handler that should be configured
+ */
+ public ConfigAgent(String configId, ConfigHandler handler) {
+ this.configURI = ConfigURI.createFromId(configId);
+ this.handler = handler;
+ }
+
+ /**
+ * Create a config agent that will obtain config for the given handler and configure it programmatically.
+ *
+ * @param configURI the config URI we want to use
+ * @param handler the handler that should be configured
+ */
+ public ConfigAgent(ConfigURI configURI, ConfigHandler handler) {
+ this.configURI = configURI;
+ this.handler = handler;
+ }
+
+ /**
+ * Create a config agent that will configure the given handler with the given config.
+ *
+ * @param config the config we want to use
+ * @param handler the handler that should be configured
+ */
+ public ConfigAgent(MessagebusConfig config, ConfigHandler handler) {
+ this.configURI = null;
+ this.handler = handler;
+ configure(config);
+ }
+
+ /**
+ * Force reload config. Only necessary for testing or if subscribing to
+ * config using files.
+ */
+ public void reload(long generation) {
+ if (subscriber != null) {
+ subscriber.reload(generation);
+ }
+ }
+
+ /**
+ * Start listening for config updates. This method will not return until the handler has been configured at least
+ * once unless an exception is thrown.
+ */
+ public void subscribe() {
+ if (configURI != null) {
+ subscriber = new ConfigSubscriber(configURI.getSource());
+ subscriber.subscribe(this, MessagebusConfig.class, configURI.getConfigId());
+ }
+ }
+
+ @Override
+ public void configure(MessagebusConfig config) {
+ RoutingSpec routing = new RoutingSpec();
+ for (int table = 0; table < config.routingtable().size(); table++) {
+ MessagebusConfig.Routingtable tableConfig = config.routingtable(table);
+ RoutingTableSpec tableSpec = new RoutingTableSpec(tableConfig.protocol());
+ for (int hop = 0; hop < tableConfig.hop().size(); hop++) {
+ MessagebusConfig.Routingtable.Hop hopConfig = tableConfig.hop(hop);
+ HopSpec hopSpec = new HopSpec(hopConfig.name(), hopConfig.selector());
+ for (int recipient = 0; recipient < hopConfig.recipient().size(); recipient++) {
+ hopSpec.addRecipient(hopConfig.recipient(recipient));
+ }
+ hopSpec.setIgnoreResult(hopConfig.ignoreresult());
+ tableSpec.addHop(hopSpec);
+ }
+ for (int route = 0; route < tableConfig.route().size(); route++) {
+ MessagebusConfig.Routingtable.Route routeConfig = tableConfig.route(route);
+ RouteSpec routeSpec = new RouteSpec(routeConfig.name());
+ for (int hop = 0; hop < routeConfig.hop().size(); hop++) {
+ routeSpec.addHop(routeConfig.hop(hop));
+ }
+ tableSpec.addRoute(routeSpec);
+ }
+ routing.addTable(tableSpec);
+ }
+ handler.setupRouting(routing);
+ }
+
+ /**
+ * Shuts down the config agent by unsubscribing to the messagebus config.
+ */
+ public void shutdown() {
+ if (subscriber != null) {
+ subscriber.close();
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/ConfigHandler.java b/messagebus/src/main/java/com/yahoo/messagebus/ConfigHandler.java
new file mode 100644
index 00000000000..6e16b593b5e
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/ConfigHandler.java
@@ -0,0 +1,22 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.messagebus.routing.RoutingSpec;
+
+/**
+ * This class declares those methods required to be a handler for an instance of the {@link ConfigAgent} class.
+ * Instead of declaring separate subscribers and handlers for all types of configurations, this pair is intended to hold
+ * everything. Extend this handler whenever new configs are added to {@link ConfigAgent}.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface ConfigHandler {
+
+ /**
+ * Sets the routing specification for this client. This will be done synchronously during initialization, and then
+ * subsequently whenever an updated configuration is available.
+ *
+ * @param spec The routing specification.
+ */
+ public void setupRouting(RoutingSpec spec);
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/DestinationSession.java b/messagebus/src/main/java/com/yahoo/messagebus/DestinationSession.java
new file mode 100644
index 00000000000..7104d596015
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/DestinationSession.java
@@ -0,0 +1,143 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.log.LogLevel;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+/**
+ * A session supporting receiving and replying to messages. A destination is expected to reply to every message
+ * received.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class DestinationSession implements MessageHandler {
+
+ private static Logger log = Logger.getLogger(DestinationSession.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final String name;
+ private final boolean broadcastName;
+ private final MessageBus mbus;
+ private final MessageHandler msgHandler;
+
+ /**
+ * This constructor is package private since only MessageBus is supposed to instantiate it.
+ *
+ * @param mbus The message bus that created this instance.
+ * @param params The parameter object for this session.
+ */
+ DestinationSession(MessageBus mbus, DestinationSessionParams params) {
+ this.mbus = mbus;
+ this.name = params.getName();
+ this.broadcastName = params.getBroadcastName();
+ this.msgHandler = params.getMessageHandler();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "DestinationSession destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Sets the destroyed flag to true. The very first time this method is called, it cleans up all its dependencies.
+ * Even if you retain a reference to this object, all of its content is allowed to be garbage collected.
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (!destroyed.getAndSet(true)) {
+ close();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method unregisters this session from message bus, effectively disabling any more messages from being
+ * delivered to the message handler. After unregistering, this method calls {@link com.yahoo.messagebus.MessageBus#sync()}
+ * as to ensure that there are no threads currently entangled in the handler.
+ *
+ * This method will deadlock if you call it from the message handler.
+ */
+ public void close() {
+ mbus.unregisterSession(name, broadcastName);
+ mbus.sync();
+ }
+
+ /**
+ * Conveniece method for acknowledging a message back to the sender.
+ *
+ * This is equivalent to:
+ * <pre>
+ * Reply ack = new EmptyReply();
+ * ack.swapState(msg);
+ * reply(ack);
+ * </pre>
+ *
+ * Messages should be acknowledged when
+ * <ul>
+ * <li>this destination has safely and permanently applied the message, or
+ * <li>an intermediate determines that the purpose of the message is fullfilled without forwarding the message
+ * </ul>
+ *
+ * @param msg The message to acknowledge back to the sender.
+ * @see #reply
+ */
+ public void acknowledge(Message msg) {
+ Reply ack = new EmptyReply();
+ msg.swapState(ack);
+ reply(ack);
+ }
+
+ /**
+ * Sends a reply to a message. The reply will propagate back to the original sender, prefering the same route as it
+ * used to reach the detination.
+ *
+ * @param reply The reply, created from the message this is a reply to.
+ */
+ public void reply(Reply reply) {
+ ReplyHandler handler = reply.popHandler();
+ handler.handleReply(reply);
+ }
+
+ /**
+ * Returns the message handler of this session.
+ *
+ * @return The message handler.
+ */
+ public MessageHandler getMessageHandler() {
+ return msgHandler;
+ }
+
+ /**
+ * Returns the connection spec string for this session. This returns a combination of the owning message bus' own
+ * spec string and the name of this session.
+ *
+ * @return The connection string.
+ */
+ public String getConnectionSpec() {
+ return mbus.getConnectionSpec() + "/" + name;
+ }
+
+ /**
+ * Returns the name of this session.
+ *
+ * @return The session name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ msgHandler.handleMessage(msg);
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/DestinationSessionParams.java b/messagebus/src/main/java/com/yahoo/messagebus/DestinationSessionParams.java
new file mode 100755
index 00000000000..5a721b26e4f
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/DestinationSessionParams.java
@@ -0,0 +1,99 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus#createDestinationSession(DestinationSessionParams)},
+ * all parameters are held by this class. This class has reasonable default values for each parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class DestinationSessionParams {
+
+ // The session name to register with message bus.
+ private String name = "destination";
+
+ // Whether or not to broadcast name on network.
+ private boolean broadcastName = true;
+
+ // The handler to receive incoming messages.
+ private MessageHandler handler = null;
+
+ /**
+ * Constructs a new instance of this class with default values.
+ */
+ public DestinationSessionParams() {
+ // empty
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param params The object to copy.
+ */
+ public DestinationSessionParams(DestinationSessionParams params) {
+ name = params.name;
+ broadcastName = params.broadcastName;
+ handler = params.handler;
+ }
+
+ /**
+ * Returns the name to register with message bus.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name to register with message bus.
+ *
+ * @param name The name to set.
+ * @return This, to allow chaining.
+ */
+ public DestinationSessionParams setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Returns whether or not to broadcast the name of this session on the network.
+ *
+ * @return True to broadcast, false otherwise.
+ */
+ public boolean getBroadcastName() {
+ return broadcastName;
+ }
+
+ /**
+ * Sets whether or not to broadcast the name of this session on the network.
+ *
+ * @param broadcastName True to broadcast, false otherwise.
+ * @return This, to allow chaining.
+ */
+ public DestinationSessionParams setBroadcastName(boolean broadcastName) {
+ this.broadcastName = broadcastName;
+ return this;
+ }
+
+ /**
+ * Returns the handler to receive incoming messages.
+ *
+ * @return The handler.
+ */
+ public MessageHandler getMessageHandler() {
+ return handler;
+ }
+
+ /**
+ * Sets the handler to recive incoming messages.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ public DestinationSessionParams setMessageHandler(MessageHandler handler) {
+ this.handler = handler;
+ return this;
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/DynamicThrottlePolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/DynamicThrottlePolicy.java
new file mode 100644
index 00000000000..9d6af3a84a9
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/DynamicThrottlePolicy.java
@@ -0,0 +1,256 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.concurrent.SystemTimer;
+import com.yahoo.concurrent.Timer;
+import com.yahoo.log.LogLevel;
+import java.util.logging.Logger;
+
+/**
+ * This is an implementatin of the {@link ThrottlePolicy} that offers dynamic limits to the number of pending messages a
+ * {@link SourceSession} is allowed to have.
+ *
+ * <b>NOTE:</b> By context, "pending" is refering to the number of sent messages that have not been replied to yet.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class DynamicThrottlePolicy extends StaticThrottlePolicy {
+
+ private static final long IDLE_TIME_MILLIS = 60000;
+ private final Timer timer;
+ private int numSent = 0;
+ private int numOk = 0;
+ private double resizeRate = 3;
+ private long resizeTime = 0;
+ private long timeOfLastMessage;
+ private double efficiencyThreshold = 1.0;
+ private double windowSizeIncrement = 20;
+ private double windowSize = windowSizeIncrement;
+ private double minWindowSize = windowSizeIncrement;
+ private double maxWindowSize = Integer.MAX_VALUE;
+ private double windowSizeBackOff = 0.9;
+ private double weight = 1.0;
+ private double localMaxThroughput = 0;
+ private double maxThroughput = 0;
+ private static final Logger log = Logger.getLogger(DynamicThrottlePolicy.class.getName());
+
+ /**
+ * Constructs a new instance of this policy and sets the appropriate default values of member data.
+ */
+ public DynamicThrottlePolicy() {
+ this(SystemTimer.INSTANCE);
+ }
+
+ /**
+ * Constructs a new instance of this class using the given clock to calculate efficiency.
+ *
+ * @param timer The timer to use.
+ */
+ public DynamicThrottlePolicy(Timer timer) {
+ this.timer = timer;
+ this.timeOfLastMessage = timer.milliTime();
+ }
+
+ public double getWindowSizeIncrement() {
+ return windowSizeIncrement;
+ }
+
+ public double getWindowSizeBackOff() {
+ return windowSizeBackOff;
+ }
+
+ public void setMaxThroughput(double maxThroughput) {
+ this.maxThroughput = maxThroughput;
+ }
+
+ @Override
+ public boolean canSend(Message msg, int pendingCount) {
+ if (!super.canSend(msg, pendingCount)) {
+ return false;
+ }
+ long time = timer.milliTime();
+ double elapsed = (time - timeOfLastMessage);
+ if (elapsed > IDLE_TIME_MILLIS) {
+ windowSize = Math.min(windowSize, pendingCount + windowSizeIncrement);
+ }
+ timeOfLastMessage = time;
+ return pendingCount < windowSize;
+ }
+
+ @Override
+ public void processMessage(Message msg) {
+ super.processMessage(msg);
+ if (++numSent < windowSize * resizeRate) {
+ return;
+ }
+
+ long time = timer.milliTime();
+ double elapsed = time - resizeTime;
+ resizeTime = time;
+
+ double throughput = numOk / elapsed;
+ numSent = 0;
+ numOk = 0;
+
+ if (log.isLoggable(LogLevel.DEBUG)) {
+ log.log(LogLevel.DEBUG, "windowSize " + windowSize + " throughput " + throughput);
+ }
+
+ if (maxThroughput > 0 && throughput > maxThroughput * 0.95) {
+ // No need to increase window when we're this close to max.
+ } else if (throughput > localMaxThroughput * 1.01) {
+ localMaxThroughput = throughput;
+ windowSize += weight*windowSizeIncrement;
+ } else {
+ // scale up/down throughput for comparing to window size
+ double period = 1;
+ while(throughput * period/windowSize < 2) {
+ period *= 10;
+ }
+ while(throughput * period/windowSize > 2) {
+ period *= 0.1;
+ }
+ double efficiency = throughput*period/windowSize;
+ if (efficiency < efficiencyThreshold) {
+ double newSize = Math.min(windowSize,throughput * period);
+ windowSize = Math.min(windowSize * windowSizeBackOff, windowSize - 2* windowSizeIncrement);
+ localMaxThroughput = 0;
+ } else {
+ windowSize += weight*windowSizeIncrement;
+ }
+ }
+ windowSize = Math.max(minWindowSize, windowSize);
+ windowSize = Math.min(maxWindowSize, windowSize);
+ }
+
+ @Override
+ public void processReply(Reply reply) {
+ super.processReply(reply);
+ if (!reply.hasErrors()) {
+ ++numOk;
+ }
+ }
+
+ /**
+ * Sets the lower efficiency threshold at which the algorithm should perform window size back off. Efficiency is
+ * the correlation between throughput and window size. The algorithm will increase the window size until efficiency
+ * drops below the efficiency of the local maxima times this value.
+ *
+ * @param efficiencyThreshold The limit to set.
+ * @return This, to allow chaining.
+ * @see #setWindowSizeBackOff(double)
+ */
+ public DynamicThrottlePolicy setEfficiencyThreshold(double efficiencyThreshold) {
+ this.efficiencyThreshold = efficiencyThreshold;
+ return this;
+ }
+
+ /**
+ * Sets the step size used when increasing window size.
+ *
+ * @param windowSizeIncrement The step size to set.
+ * @return This, to allow chaining.
+ */
+ public DynamicThrottlePolicy setWindowSizeIncrement(double windowSizeIncrement) {
+ this.windowSizeIncrement = windowSizeIncrement;
+ return this;
+ }
+
+ /**
+ * Sets the factor of window size to back off to when the algorithm determines that efficiency is not increasing.
+ * A value of 1 means that there is no back off from the local maxima, and means that the algorithm will fail to
+ * reduce window size to something lower than a previous maxima. This value is capped to the [0, 1] range.
+ *
+ * @param windowSizeBackOff The back off to set.
+ * @return This, to allow chaining.
+ */
+ public DynamicThrottlePolicy setWindowSizeBackOff(double windowSizeBackOff) {
+ this.windowSizeBackOff = Math.max(0, Math.min(1, windowSizeBackOff));
+ return this;
+ }
+
+ /**
+ * Sets the rate at which the window size is updated. The larger the value, the less responsive the resizing
+ * becomes. However, the smaller the value, the less accurate the measurements become.
+ *
+ * @param resizeRate The rate to set.
+ * @return This, to allow chaining.
+ */
+ public DynamicThrottlePolicy setResizeRate(double resizeRate) {
+ this.resizeRate = resizeRate;
+ return this;
+ }
+
+ /**
+ * Sets the weight for this client. The larger the value, the more resources
+ * will be allocated to this clients. Resources are shared between clients
+ * proportiannally to their weights.
+ *
+ * @param weight The weight to set.
+ * @return This, to allow chaining.
+ */
+ public DynamicThrottlePolicy setWeight(double weight) {
+ this.weight = weight;
+ return this;
+ }
+
+ /**
+ * Sets the maximium number of pending operations allowed at any time, in
+ * order to avoid using too much resources.
+ *
+ * @param max The max to set.
+ * @return This, to allow chaining.
+ */
+ public DynamicThrottlePolicy setMaxWindowSize(double max) {
+ this.maxWindowSize = max;
+ return this;
+ }
+
+ /**
+ * Get the maximum number of pending operations allowed at any time.
+ *
+ * @return The maximum number of operations.
+ */
+ public double getMaxWindowSize() {
+ return maxWindowSize;
+ }
+
+
+ /**
+ * Sets the minimium number of pending operations allowed at any time, in
+ * order to keep a level of performance.
+ *
+ * @param min The min to set.
+ * @return This, to allow chaining.
+ */
+ public DynamicThrottlePolicy setMinWindowSize(double min) {
+ this.minWindowSize = min;
+ return this;
+ }
+
+ /**
+ * Get the minimum number of pending operations allowed at any time.
+ *
+ * @return The minimum number of operations.
+ */
+ public double getMinWindowSize() {
+ return minWindowSize;
+ }
+
+ public DynamicThrottlePolicy setMaxPendingCount(int maxCount) {
+ super.setMaxPendingCount(maxCount);
+ maxWindowSize = maxCount;
+ return this;
+ }
+
+
+ /**
+ * Returns the maximum number of pending messages allowed.
+ *
+ * @return The max limit.
+ */
+ public int getMaxPendingCount() {
+ return (int)windowSize;
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/EmptyReply.java b/messagebus/src/main/java/com/yahoo/messagebus/EmptyReply.java
new file mode 100644
index 00000000000..cb674920458
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/EmptyReply.java
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.text.Utf8String;
+
+/**
+ * The empty reply is the only concrete implementation of a message that is offered by the MessageBus. It is used to
+ * generate replies to events that occur within the messagebus, and since the messagebus by design knows nothing about
+ * the messages that have been implemented by the users it requires a class such as this.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class EmptyReply extends Reply {
+ private final Utf8String PROTOCOL = new Utf8String("");
+
+ /**
+ * Implements the getType() function of the root class Routable to identify this reply as the reserved type '0'.
+ *
+ * @return The number '0'.
+ */
+ public int getType() {
+ return 0;
+ }
+
+ /**
+ * Implements the getProtocol() function of Routable to identify this reply as the reserved type. This is done by an
+ * empty string.
+ *
+ * @return The string "".
+ */
+ public Utf8String getProtocol() {
+ return PROTOCOL;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Error.java b/messagebus/src/main/java/com/yahoo/messagebus/Error.java
new file mode 100644
index 00000000000..b66c398224c
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Error.java
@@ -0,0 +1,86 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * This class implements the pair (code, message) that is used in Reply to hold errors.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class Error {
+
+ private final int code;
+ private final String message;
+ private final String service;
+
+ /**
+ * This is the constructor used by anyone adding an error to a message. One does not manually need to set the
+ * service name of an error, so ignore the other constructor when creating your own error instance.
+ *
+ * @param code The numerical code of this error.
+ * @param message The description of this error.
+ */
+ public Error(int code, String message) {
+ this.code = code;
+ this.message = message;
+ service = null;
+ }
+
+ /**
+ * This constructor is used by the network layer to properly tag deserialized errors with the hostname of whatever
+ * service produced the error. This constructor should NOT be used when manually creating errors.
+ *
+ * @param code The numerical code of this error.
+ * @param message The description of this error.
+ * @param service The service name of this error.
+ */
+ public Error(int code, String message, String service) {
+ this.code = code;
+ this.message = message;
+ this.service = service;
+ }
+
+ /**
+ * Return the numerical code of this error.
+ *
+ * @return The numerical code.
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * Return the description of this error.
+ *
+ * @return The description.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Returns the name of the service on which this error occured.
+ *
+ * @return The service name.
+ */
+ public String getService() {
+ return service;
+ }
+
+ /**
+ * Returns whether or not this error is fatal, i.e. getCode() &gt;= ErrorCode.FATAL_ERROR.
+ *
+ * @return True, if this error is fatal.
+ */
+ public boolean isFatal() {
+ return code >= ErrorCode.FATAL_ERROR;
+ }
+
+ @Override
+ public String toString() {
+ String name = ErrorCode.getName(code);
+ return "[" +
+ (name != null ? name : code) + " @ " +
+ (service != null ? service : "localhost") +
+ "]: " + message;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/ErrorCode.java b/messagebus/src/main/java/com/yahoo/messagebus/ErrorCode.java
new file mode 100644
index 00000000000..5999fcb9521
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/ErrorCode.java
@@ -0,0 +1,129 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * This interface contains the reserved error codes that are used for errors that occur within the messagebus.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class ErrorCode {
+
+ /** The code is here for completeness. */
+ public static final int NONE = 0;
+
+ /** A general transient error, resending is possible. */
+ public static final int TRANSIENT_ERROR = 100000;
+
+ /** Sending was rejected because throttler capacity is full. */
+ public static final int SEND_QUEUE_FULL = TRANSIENT_ERROR + 1;
+
+ /** No addresses found for the services of the message route. */
+ public static final int NO_ADDRESS_FOR_SERVICE = TRANSIENT_ERROR + 2;
+
+ /** A connection problem occured while sending. */
+ public static final int CONNECTION_ERROR = TRANSIENT_ERROR + 3;
+
+ /** The session specified for the message is unknown. */
+ public static final int UNKNOWN_SESSION = TRANSIENT_ERROR + 4;
+
+ /** The recipient session is busy. */
+ public static final int SESSION_BUSY = TRANSIENT_ERROR + 5;
+
+ /** Sending aborted by route verification. */
+ public static final int SEND_ABORTED = TRANSIENT_ERROR + 6;
+
+ /** Version handshake failed for any reason. */
+ public static final int HANDSHAKE_FAILED = TRANSIENT_ERROR + 7;
+
+ /** An application specific transient error. */
+ public static final int APP_TRANSIENT_ERROR = TRANSIENT_ERROR + 50000;
+
+ /** A general non-recoverable error, resending is not possible. */
+ public static final int FATAL_ERROR = 200000;
+
+ /** Sending was rejected because throttler is closed. */
+ public static final int SEND_QUEUE_CLOSED = FATAL_ERROR + 1;
+
+ /** The route of the message is illegal. */
+ public static final int ILLEGAL_ROUTE = FATAL_ERROR + 2;
+
+ /** No services found for the message route. */
+ public static final int NO_SERVICES_FOR_ROUTE = FATAL_ERROR + 3;
+
+ /** The selected service was out of service. */
+ public static final int SERVICE_OOS = FATAL_ERROR + 4;
+
+ /** An error occured while encoding the message. */
+ public static final int ENCODE_ERROR = FATAL_ERROR + 5;
+
+ /** A fatal network error occured while sending. */
+ public static final int NETWORK_ERROR = FATAL_ERROR + 6;
+
+ /** The protocol specified for the message is unknown. */
+ public static final int UNKNOWN_PROTOCOL = FATAL_ERROR + 7;
+
+ /** An error occured while decoding the message. */
+ public static final int DECODE_ERROR = FATAL_ERROR + 8;
+
+ /** A timeout occured while sending. */
+ public static final int TIMEOUT = FATAL_ERROR + 9;
+
+ /** The target is running an incompatible version. */
+ public static final int INCOMPATIBLE_VERSION = FATAL_ERROR + 10;
+
+ /** The policy specified in a route is unknown. */
+ public static final int UNKNOWN_POLICY = FATAL_ERROR + 11;
+
+ /** The network was shut down when attempting to send. */
+ public static final int NETWORK_SHUTDOWN = FATAL_ERROR + 12;
+
+ /** Exception thrown by routing policy. */
+ public static final int POLICY_ERROR = FATAL_ERROR + 13;
+
+ /** An error occured while sequencing a message. */
+ public static final int SEQUENCE_ERROR = FATAL_ERROR + 14;
+
+ /** An application specific non-recoverable error. */
+ public static final int APP_FATAL_ERROR = FATAL_ERROR + 50000;
+
+ /** No error codes are allowed to be this big. */
+ public static final int ERROR_LIMIT = APP_FATAL_ERROR + 50000;
+
+ /**
+ * Translates the given error code into its symbolic name.
+ *
+ * @param error The error code to translate.
+ * @return The symbolic name.
+ */
+ public static String getName(int error) {
+ switch (error) {
+ case APP_FATAL_ERROR : return "APP_FATAL_ERROR";
+ case APP_TRANSIENT_ERROR : return "APP_TRANSIENT_ERROR";
+ case CONNECTION_ERROR : return "CONNECTION_ERROR";
+ case DECODE_ERROR : return "DECODE_ERROR";
+ case ENCODE_ERROR : return "ENCODE_ERROR";
+ case FATAL_ERROR : return "FATAL_ERROR";
+ case HANDSHAKE_FAILED : return "HANDSHAKE_FAILED";
+ case ILLEGAL_ROUTE : return "ILLEGAL_ROUTE";
+ case INCOMPATIBLE_VERSION : return "INCOMPATIBLE_VERSION";
+ case NETWORK_ERROR : return "NETWORK_ERROR";
+ case NETWORK_SHUTDOWN : return "NETWORK_SHUTDOWN";
+ case NO_ADDRESS_FOR_SERVICE : return "NO_ADDRESS_FOR_SERVICE";
+ case NO_SERVICES_FOR_ROUTE : return "NO_SERVICES_FOR_ROUTE";
+ case NONE : return "NONE";
+ case POLICY_ERROR : return "POLICY_ERROR";
+ case SEND_ABORTED : return "SEND_ABORTED";
+ case SEND_QUEUE_CLOSED : return "SEND_QUEUE_CLOSED";
+ case SEND_QUEUE_FULL : return "SEND_QUEUE_FULL";
+ case SEQUENCE_ERROR : return "SEQUENCE_ERROR";
+ case SERVICE_OOS : return "SERVICE_OOS";
+ case SESSION_BUSY : return "SESSION_BUSY";
+ case TIMEOUT : return "TIMEOUT";
+ case TRANSIENT_ERROR : return "TRANSIENT_ERROR";
+ case UNKNOWN_POLICY : return "UNKNOWN_POLICY";
+ case UNKNOWN_PROTOCOL : return "UNKNOWN_PROTOCOL";
+ case UNKNOWN_SESSION : return "UNKNOWN_SESSION";
+ default : return "UNKNOWN(" + error + ")";
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/IntermediateSession.java b/messagebus/src/main/java/com/yahoo/messagebus/IntermediateSession.java
new file mode 100644
index 00000000000..2ebb72e2518
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/IntermediateSession.java
@@ -0,0 +1,141 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.log.LogLevel;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+/**
+ * A session which supports receiving, forwarding and acknowledgement of messages. An intermediate session is expacted
+ * to either forward or acknowledge every message received.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class IntermediateSession implements MessageHandler, ReplyHandler {
+
+ private static final Logger log = Logger.getLogger(IntermediateSession.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final String name;
+ private final boolean broadcastName;
+ private final MessageHandler msgHandler;
+ private final ReplyHandler replyHandler;
+ private final MessageBus mbus;
+
+ /**
+ * This constructor is declared package private since only MessageBus is supposed to instantiate it.
+ *
+ * @param mbus The message bus that created this instance.
+ * @param params The parameter object for this session.
+ */
+ IntermediateSession(MessageBus mbus, IntermediateSessionParams params) {
+ this.mbus = mbus;
+ this.name = params.getName();
+ this.broadcastName = params.getBroadcastName();
+ this.msgHandler = params.getMessageHandler();
+ this.replyHandler= params.getReplyHandler();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "IntermediateSession destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Sets the destroyed flag to true. The very first time this method is called, it cleans up all its dependencies.
+ * Even if you retain a reference to this object, all of its content is allowed to be garbage collected.
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (!destroyed.getAndSet(true)) {
+ close();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method unregisters this session from message bus, effectively disabling any more messages from being
+ * delivered to the message handler. After unregistering, this method calls {@link com.yahoo.messagebus.MessageBus#sync()}
+ * as to ensure that there are no threads currently entangled in the handler.
+ *
+ * This method will deadlock if you call it from the message or reply handler.
+ */
+ public void close() {
+ mbus.unregisterSession(name, broadcastName);
+ mbus.sync();
+ }
+
+ /**
+ * Forwards a routable to the next hop in its route. This method will never block.
+ * @param routable the routable to forward.
+ */
+ public void forward(Routable routable) {
+ if (routable instanceof Reply) {
+ Reply reply = (Reply)routable;
+ ReplyHandler handler = reply.popHandler();
+ handler.handleReply(reply);
+ } else {
+ routable.pushHandler(this);
+ mbus.handleMessage((Message)routable);
+ }
+ }
+
+ /**
+ * Returns the message handler of this session.
+ *
+ * @return The message handler.
+ */
+ public MessageHandler getMessageHandler() {
+ return msgHandler;
+ }
+
+ /**
+ * Returns the reply handler of this session.
+ *
+ * @return The reply handler.
+ */
+ public ReplyHandler getReplyHandler() {
+ return replyHandler;
+ }
+
+ /**
+ * Returns the connection spec string for this session. This returns a combination of the owning message bus' own
+ * spec string and the name of this session.
+ *
+ * @return The connection string.
+ */
+ public String getConnectionSpec() {
+ return mbus.getConnectionSpec() + "/" + name;
+ }
+
+ /**
+ * Returns the name of this session.
+ *
+ * @return The session name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ msgHandler.handleMessage(msg);
+ }
+
+ @Override
+ public void handleReply(Reply reply) {
+ if (destroyed.get()) {
+ reply.discard();
+ } else {
+ replyHandler.handleReply(reply);
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/IntermediateSessionParams.java b/messagebus/src/main/java/com/yahoo/messagebus/IntermediateSessionParams.java
new file mode 100755
index 00000000000..fb3f1b3bb4c
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/IntermediateSessionParams.java
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus#createIntermediateSession(IntermediateSessionParams)},
+ * all parameters are held by this class. This class has reasonable default values for each parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IntermediateSessionParams {
+
+ // The session name to register with message bus.
+ private String name = "intermediate";
+
+ // Whether or not to broadcast name on network.
+ private boolean broadcastName = true;
+
+ // The handler to receive incoming replies.
+ private ReplyHandler replyHandler = null;
+
+ // The handler to receive incoming messages.
+ private MessageHandler msgHandler = null;
+
+ /**
+ * Constructs a new instance of this class with default values.
+ */
+ public IntermediateSessionParams() {
+ // empty
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param params The object to copy.
+ */
+ public IntermediateSessionParams(IntermediateSessionParams params) {
+ name = params.name;
+ broadcastName = params.broadcastName;
+ replyHandler = params.replyHandler;
+ msgHandler = params.msgHandler;
+ }
+
+ /**
+ * Returns the name to register with message bus.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name to register with message bus.
+ *
+ * @param name The name to set.
+ * @return This, to allow chaining.
+ */
+ public IntermediateSessionParams setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Returns whether or not to broadcast the name of this session on the network.
+ *
+ * @return True to broadcast, false otherwise.
+ */
+ public boolean getBroadcastName() {
+ return broadcastName;
+ }
+
+ /**
+ * Returns the handler to receive incoming replies.
+ *
+ * @return The handler.
+ */
+ public ReplyHandler getReplyHandler() {
+ return replyHandler;
+ }
+
+ /**
+ * Sets the handler to recive incoming replies.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ public IntermediateSessionParams setReplyHandler(ReplyHandler handler) {
+ replyHandler = handler;
+ return this;
+ }
+
+ /**
+ * Returns the handler to receive incoming messages.
+ *
+ * @return The handler.
+ */
+ public MessageHandler getMessageHandler() {
+ return msgHandler;
+ }
+
+ /**
+ * Sets the handler to recive incoming messages.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ public IntermediateSessionParams setMessageHandler(MessageHandler handler) {
+ msgHandler = handler;
+ return this;
+ }
+
+ /**
+ * Sets whether or not to broadcast the name of this session on the network.
+ *
+ * @param broadcastName True to broadcast, false otherwise.
+ * @return This, to allow chaining.
+ */
+ public IntermediateSessionParams setBroadcastName(boolean broadcastName) {
+ this.broadcastName = broadcastName;
+ return this;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Message.java b/messagebus/src/main/java/com/yahoo/messagebus/Message.java
new file mode 100644
index 00000000000..8ebb1f2a227
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Message.java
@@ -0,0 +1,244 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.concurrent.SystemTimer;
+import com.yahoo.messagebus.routing.Route;
+
+/**
+ * <p>A message is a child of Routable, it is not a reply, and it has a sequencing identifier. Furthermore, a message
+ * contains a retry counter that holds what retry the message is currently on. See the method comment {@link #getRetry}
+ * for more information.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class Message extends Routable {
+
+ private Route route = null;
+ private long timeReceived = 0;
+ private long timeRemaining = 0;
+ private boolean retryEnabled = true;
+ private int retry = 0;
+
+ @Override
+ public void swapState(Routable rhs) {
+ super.swapState(rhs);
+ if (rhs instanceof Message) {
+ Message msg = (Message)rhs;
+
+ Route route = this.route;
+ this.route = msg.route;
+ msg.route = route;
+
+ boolean retryEnabled = this.retryEnabled;
+ this.retryEnabled = msg.retryEnabled;
+ msg.retryEnabled = retryEnabled;
+
+ int retry = this.retry;
+ this.retry = msg.retry;
+ msg.retry = retry;
+
+ long timeReceived = this.timeReceived;
+ this.timeReceived = msg.timeReceived;
+ msg.timeReceived = timeReceived;
+
+ long timeRemaining = this.timeRemaining;
+ this.timeRemaining = msg.timeRemaining;
+ msg.timeRemaining = timeRemaining;
+ }
+ }
+
+ /**
+ * <p>Return the route of this routable.</p>
+ *
+ * @return The route.
+ */
+ public Route getRoute() {
+ return route;
+ }
+
+ /**
+ * <p>Set a new route for this routable.</p>
+ *
+ * @param route The new route.
+ * @return This, to allow chaining.
+ */
+ public Message setRoute(Route route) {
+ this.route = new Route(route);
+ return this;
+ }
+
+ /**
+ * <p>Returns the timestamp for when this message was last seen by message bus. If you are using this to determine
+ * message expiration, you should use {@link #isExpired()} instead.</p>
+ *
+ * @return The timestamp this was last seen.
+ */
+ public long getTimeReceived() {
+ return timeReceived;
+ }
+
+ /**
+ * <p>Sets the timestamp for when this message was last seen by message bus to the given time in milliseconds since
+ * epoch. Please see comment on {@link #isExpired()} for more information on how to determine whether or not a
+ * message has expired. You should never need to call this method yourself, as it is touched automatically whenever
+ * message bus encounters a new message.</p>
+ *
+ * @param timeReceived The time received in milliseconds.
+ * @return This, to allow chaining.
+ */
+ public Message setTimeReceived(long timeReceived) {
+ this.timeReceived = timeReceived;
+ return this;
+ }
+
+ /**
+ * <p>This is a convenience method to call {@link #setTimeReceived(long)} passing the current time as argument.</p>
+ *
+ * @return This, to allow chaining.
+ */
+ public Message setTimeReceivedNow() {
+ return setTimeReceived(SystemTimer.INSTANCE.milliTime());
+ }
+
+ /**
+ * <p>Returns the number of milliseconds that remain before this message times out. This value is only updated by
+ * the network layer, and is therefore not current. If you are trying to determine message expiration, use {@link
+ * #isExpired()} instead.</p>
+ *
+ * @return The remaining time in milliseconds.
+ */
+ public long getTimeRemaining() {
+ return timeRemaining;
+ }
+
+ /**
+ * <p>Sets the numer of milliseconds that remain before this message times out. Please see comment on {@link
+ * #isExpired()} for more information on how to determine whether or not a message has expired.</p>
+ *
+ * @param timeRemaining The number of milliseconds until expiration.
+ * @return This, to allow chaining.
+ */
+ public Message setTimeRemaining(long timeRemaining) {
+ this.timeRemaining = timeRemaining;
+ return this;
+ }
+
+ /**
+ * <p>Returns the number of milliseconds that remain right now before this message times out. This is a function of
+ * {@link #getTimeReceived()}, {@link #getTimeRemaining()} and current time. Whenever a message is transmitted by
+ * message bus, a new remaining time is calculated and serialized as <code>timeRemaining = timeRemaining -
+ * (currentTime - timeReceived)</code>. This means that we are doing an over-estimate of remaining time, as we are
+ * only factoring in the time used by the application above message bus.</p>
+ *
+ * @return The remaining time in milliseconds.
+ */
+ public long getTimeRemainingNow() {
+ return timeRemaining - (SystemTimer.INSTANCE.milliTime() - timeReceived);
+ }
+
+ /**
+ * <p>Returns whether or not this message has expired.</p>
+ *
+ * @return True if {@link #getTimeRemainingNow()} is less than or equal to zero.
+ */
+ public boolean isExpired() {
+ return getTimeRemainingNow() <= 0;
+ }
+
+ /**
+ * <p>Returns whether or not this message contains a sequence identifier that should be respected, i.e. whether or
+ * not this message requires sequencing.</p>
+ *
+ * @return True to enable sequencing.
+ * @see #getSequenceId()
+ */
+ public boolean hasSequenceId() {
+ return false;
+ }
+
+ /**
+ * <p>Returns the identifier used to order messages. Any two messages that have the same sequence id are ensured to
+ * arrive at the recipient in the order they were sent by the client. This value is only respected if the {@link
+ * #hasSequenceId()} method returns true.</p>
+ *
+ * @return The sequence identifier.
+ */
+ public long getSequenceId() {
+ return 0;
+ }
+
+ /**
+ * <p>Returns whether or not this message contains a sequence bucket that should be respected, i.e. whether or not
+ * this message requires bucket-level sequencing.</p>
+ *
+ * @return True to enable bucket sequencing.
+ * @see #getBucketSequence()
+ */
+ public boolean hasBucketSequence() {
+ return false;
+ }
+
+ /**
+ * <p>Returns the identifier used to order message buckets. Any two messages that have the same bucket sequence are
+ * ensured to arrive at the NEXT peer in the order they were sent by THIS peer. This value is only respected if the
+ * {@link #hasBucketSequence()} method returns true.</p>
+ *
+ * @return The bucket sequence.
+ */
+ public long getBucketSequence() {
+ return 0;
+ }
+
+ /**
+ * <p>Obtain the approximate size of this message object in bytes. This enables messagebus to track the size of the
+ * send queue in both memory usage and item count. This method returns 1 by default, and must be overridden to
+ * enable message size tracking.</p>
+ *
+ * @return 1
+ */
+ public int getApproxSize() {
+ return 1;
+ }
+
+ /**
+ * <p>Sets whether or not this message can be resent.</p>
+ *
+ * @param enabled Resendable flag.
+ */
+ public void setRetryEnabled(boolean enabled) {
+ retryEnabled = enabled;
+ }
+
+ /**
+ * <p>Returns whether or not this message can be resent.</p>
+ *
+ * @return True if this can be resent.
+ */
+ public boolean getRetryEnabled() {
+ return retryEnabled;
+ }
+
+ /**
+ * <p>Returns the number of times the sending of this message has been retried. This is available for inspection so
+ * that clients may implement logic to control resending.</p>
+ *
+ * @return The retry count.
+ * @see Reply#setRetryDelay This method can be used to request resending that differs from the default.
+ */
+ public int getRetry() {
+ return retry;
+ }
+
+ /**
+ * <p>Sets the number of times the sending of this message has been retried. This method only makes sense to modify
+ * BEFORE sending it, since its value is not serialized back into any reply that it may create.</p>
+ *
+ * @param retry The retry count.
+ * @return This, to allow chaining.
+ */
+ public Message setRetry(int retry) {
+ this.retry = retry;
+ return this;
+ }
+}
+
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/MessageBus.java b/messagebus/src/main/java/com/yahoo/messagebus/MessageBus.java
new file mode 100644
index 00000000000..729bef7985f
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/MessageBus.java
@@ -0,0 +1,595 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.concurrent.CopyOnWriteHashMap;
+import com.yahoo.log.LogLevel;
+import com.yahoo.messagebus.metrics.MessageBusMetricSet;
+import com.yahoo.messagebus.network.Network;
+import com.yahoo.messagebus.network.NetworkOwner;
+import com.yahoo.messagebus.routing.*;
+import com.yahoo.text.Utf8Array;
+import com.yahoo.text.Utf8String;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Logger;
+
+/**
+ * <p>A message bus contains the factory for creating sessions to send, receive
+ * and forward messages.</p>
+ *
+ * <p>There are three types of sessions:</p>
+ * <ul><li>{@link SourceSession Source sessions} sends messages and receives
+ * replies</li>
+ * <li>{@link IntermediateSession Intermediate sessions} receives messages on
+ * their way to their final destination, and may decide to forward the messages
+ * or reply directly.</li>
+ * <li>{@link DestinationSession Destination sessions} are the final recipient
+ * of messages, and are expected to reply to every one of them, but may not
+ * forward messages.</li></ul>
+ *
+ * <p>A message bus is configured with a {@link Protocol protocol}. This table
+ * enumerates the permissible routes from intermediates to destinations and the
+ * messaging semantics of each hop.</p>
+ *
+ * <p>The responsibilities of a message bus are:</p>
+ * <ul> <li>Assign a route to every send message from its routing table</li>
+ * <li>Deliver every message it <i>accepts</i> to the next hop on its route on a
+ * best effort basis, <i>or</i> deliver a <i>failure reply</i>.</li>
+ * <li>Deliver replies back to message sources through all the intermediate
+ * hops.</li></ul>
+ *
+ * <p>A runtime will typically</p>
+ * <ul><li>Create a message bus implementation and set properties on this
+ * implementation once.</li>
+ * <li>Create sessions using that message bus many places.</li></ul>
+ *
+ * @author btratseth
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MessageBus implements ConfigHandler, NetworkOwner, MessageHandler, ReplyHandler {
+
+ private static Logger log = Logger.getLogger(MessageBus.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final ProtocolRepository protocolRepository = new ProtocolRepository();
+ private final AtomicReference<Map<String, RoutingTable>> tablesRef = new AtomicReference<Map<String, RoutingTable>>(null);
+ private final CopyOnWriteHashMap<String, MessageHandler> sessions = new CopyOnWriteHashMap<String, MessageHandler>();
+ private final Network net;
+ private final Messenger msn;
+ private final Resender resender;
+ private int maxPendingCount = 0;
+ private int maxPendingSize = 0;
+ private int pendingCount = 0;
+ private int pendingSize = 0;
+ private MessageBusMetricSet metrics = new MessageBusMetricSet();
+
+ /**
+ * <p>Convenience constructor that proxies {@link #MessageBus(Network,
+ * MessageBusParams)} by adding the given protocols to a default {@link
+ * MessageBusParams} object.</p>
+ *
+ * @param net The network to associate with.
+ * @param protocols An array of protocols to register.
+ */
+ public MessageBus(Network net, List<Protocol> protocols) {
+ this(net, new MessageBusParams().addProtocols(protocols));
+ }
+
+ /**
+ * <p>Constructs an instance of message bus. This requires a network object
+ * that it will associate with. This assignment may not change during the
+ * lifetime of this message bus.</p>
+ *
+ * @param net The network to associate with.
+ * @param params The parameters that controls this bus.
+ */
+ public MessageBus(Network net, MessageBusParams params) {
+ // Add all known protocols to the repository.
+ maxPendingCount = params.getMaxPendingCount();
+ maxPendingSize = params.getMaxPendingSize();
+ for (int i = 0, len = params.getNumProtocols(); i < len; ++i) {
+ protocolRepository.putProtocol(params.getProtocol(i));
+
+ if (params.getProtocol(i).getMetrics() != null) {
+ metrics.protocols.addMetric(params.getProtocol(i).getMetrics());
+ }
+ }
+
+ // Attach and start network.
+ this.net = net;
+ net.attach(this);
+ if (!net.waitUntilReady(120)) {
+ throw new IllegalStateException("Network failed to become ready in time.");
+ }
+
+ // Start messenger.
+ msn = new Messenger();
+
+ RetryPolicy retryPolicy = params.getRetryPolicy();
+ if (retryPolicy != null) {
+ resender = new Resender(retryPolicy);
+ msn.addRecurrentTask(new ResenderTask(resender));
+ } else {
+ resender = null;
+ }
+
+ msn.start();
+ }
+
+ /**
+ * <p>Returns the metrics used by this messagebus.</p>
+ *
+ * @return The metric set.
+ */
+ public MessageBusMetricSet getMetrics() {
+ return metrics;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "MessageBus destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * <p>Sets the destroyed flag to true. The very first time this method is
+ * called, it cleans up all its dependencies. Even if you retain a reference
+ * to this object, all of its content is allowed to be garbage
+ * collected.</p>
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (!destroyed.getAndSet(true)) {
+ protocolRepository.clearPolicyCache();
+ net.shutdown();
+ msn.destroy();
+ if (resender != null) {
+ resender.destroy();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * <p>Synchronize with internal threads. This method will handshake with all
+ * internal threads. This has the implicit effect of waiting for all active
+ * callbacks. Note that this method should never be invoked from a callback
+ * since that would make the thread wait for itself... forever. This method
+ * is typically used to untangle during session shutdown.</p>
+ */
+ public void sync() {
+ msn.sync();
+ net.sync();
+ }
+
+ /**
+ * <p>This is a convenience method to call {@link
+ * #createSourceSession(SourceSessionParams)} with default values for the
+ * {@link SourceSessionParams} object.</p>
+ *
+ * @param handler The reply handler to receive the replies for the session.
+ * @return The created session.
+ */
+ public SourceSession createSourceSession(ReplyHandler handler) {
+ return createSourceSession(new SourceSessionParams().setReplyHandler(handler));
+ }
+
+ /**
+ * <p>This is a convenience method to call {@link
+ * #createSourceSession(SourceSessionParams)} by first assigning the reply
+ * handler to the parameter object.</p>
+ *
+ * @param handler The reply handler to receive the replies for the session.
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ public SourceSession createSourceSession(ReplyHandler handler, SourceSessionParams params) {
+ return createSourceSession(new SourceSessionParams(params).setReplyHandler(handler));
+ }
+
+ /**
+ * <p>Creates a source session on top of this message bus.</p>
+ *
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ public SourceSession createSourceSession(SourceSessionParams params) {
+ if (destroyed.get()) {
+ throw new IllegalStateException("Object is destroyed.");
+ }
+ return new SourceSession(this, params);
+ }
+
+ /**
+ * <p>This is a convenience method to call {@link
+ * #createIntermediateSession(IntermediateSessionParams)} with default
+ * values for the {@link IntermediateSessionParams} object.</p>
+ *
+ * @param name The local unique name for the created session.
+ * @param broadcastName Whether or not to broadcast this session's name on
+ * the network.
+ * @param msgHandler The handler to receive the messages for the session.
+ * @param replyHandler The handler to received the replies for the session.
+ * @return The created session.
+ */
+ public IntermediateSession createIntermediateSession(String name,
+ boolean broadcastName,
+ MessageHandler msgHandler,
+ ReplyHandler replyHandler) {
+ return createIntermediateSession(
+ new IntermediateSessionParams()
+ .setName(name)
+ .setBroadcastName(broadcastName)
+ .setMessageHandler(msgHandler)
+ .setReplyHandler(replyHandler));
+ }
+
+ /**
+ * <p>Creates an intermediate session on top of this message bus using the
+ * given handlers and parameter object.</p>
+ *
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ public synchronized IntermediateSession createIntermediateSession(IntermediateSessionParams params) {
+ if (destroyed.get()) {
+ throw new IllegalStateException("Object is destroyed.");
+ }
+ if (sessions.containsKey(params.getName())) {
+ throw new IllegalArgumentException("Name '" + params.getName() + "' is not unique.");
+ }
+ IntermediateSession session = new IntermediateSession(this, params);
+ sessions.put(params.getName(), session);
+ if (params.getBroadcastName()) {
+ net.registerSession(params.getName());
+ }
+ return session;
+ }
+
+ /**
+ * <p>This is a convenience method to call {@link
+ * #createDestinationSession(DestinationSessionParams)} with default values
+ * for the {@link DestinationSessionParams} object.</p>
+ *
+ * @param name The local unique name for the created session.
+ * @param broadcastName Whether or not to broadcast this session's name on
+ * the network.
+ * @param handler The handler to receive the messages for the session.
+ * @return The created session.
+ */
+ public DestinationSession createDestinationSession(String name,
+ boolean broadcastName,
+ MessageHandler handler) {
+ return createDestinationSession(
+ new DestinationSessionParams()
+ .setName(name)
+ .setBroadcastName(broadcastName)
+ .setMessageHandler(handler));
+ }
+
+ /**
+ * <p>Creates a destination session on top of this message bus using the
+ * given handlers and parameter object.</p>
+ *
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ public synchronized DestinationSession createDestinationSession(DestinationSessionParams params) {
+ if (destroyed.get()) {
+ throw new IllegalStateException("Object is destroyed.");
+ }
+ if (sessions.containsKey(params.getName())) {
+ throw new IllegalArgumentException("Name '" + params.getName() + "' is not unique.");
+ }
+ DestinationSession session = new DestinationSession(this, params);
+ sessions.put(params.getName(), session);
+ if (params.getBroadcastName()) {
+ net.registerSession(params.getName());
+ }
+ return session;
+ }
+
+ /**
+ * <p>This method is invoked by the {@link
+ * com.yahoo.messagebus.IntermediateSession#destroy()} to unregister
+ * sessions from receiving data from message bus.</p>
+ *
+ * @param name The name of the session to remove.
+ * @param broadcastName Whether or not session name was broadcast.
+ */
+ public synchronized void unregisterSession(String name, boolean broadcastName) {
+ if (broadcastName) {
+ net.unregisterSession(name);
+ }
+ sessions.remove(name);
+ }
+
+ private boolean doAccounting() {
+ return (maxPendingCount > 0 || maxPendingSize > 0);
+ }
+ /**
+ * <p>This method handles choking input data so that message bus does not
+ * blindly accept everything. This prevents an application running
+ * out-of-memory in case it fail to choke input data itself. If this method
+ * returns false, it means that it should be rejected.</p>
+ *
+ * @param msg The message to count.
+ * @return True if the message was accepted.
+ */
+ private boolean checkPending(Message msg) {
+ boolean busy = false;
+ int size = msg.getApproxSize();
+
+ if (doAccounting()) {
+ synchronized (this) {
+ busy = ((maxPendingCount > 0 && pendingCount >= maxPendingCount) ||
+ (maxPendingSize > 0 && pendingSize >= maxPendingSize));
+ if (!busy) {
+ pendingCount++;
+ pendingSize += size;
+ }
+ }
+ }
+ if (busy) {
+ return false;
+ }
+ msg.setContext(size);
+ msg.pushHandler(this);
+ return true;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (resender != null && msg.hasBucketSequence()) {
+ deliverError(msg, ErrorCode.SEQUENCE_ERROR, "Bucket sequences not supported when resender is enabled.");
+ return;
+ }
+ SendProxy proxy = new SendProxy(this, net, resender);
+ msn.deliverMessage(msg, proxy);
+ }
+
+ @Override
+ public void handleReply(Reply reply) {
+ if (destroyed.get()) {
+ reply.discard();
+ return;
+ }
+ if (doAccounting()) {
+ synchronized (this) {
+ --pendingCount;
+ pendingSize -= (Integer)reply.getContext();
+ }
+ }
+ deliverReply(reply, reply.popHandler());
+ }
+
+ @Override
+ public void deliverMessage(Message msg, String session) {
+ MessageHandler msgHandler = sessions.get(session);
+ if (msgHandler == null) {
+ deliverError(msg, ErrorCode.UNKNOWN_SESSION, "Session '" + session + "' does not exist.");
+ } else if (!checkPending(msg)) {
+ deliverError(msg, ErrorCode.SESSION_BUSY, "Session '" + net.getConnectionSpec() + "/" + session +
+ "' is busy, try again later.");
+ } else {
+ msn.deliverMessage(msg, msgHandler);
+ }
+ }
+
+ /**
+ * <p>Adds a protocol to the internal repository of protocols, replacing any
+ * previous instance of the protocol and clearing the associated routing
+ * policy cache.</p>
+ *
+ * @param protocol The protocol to add.
+ */
+ public void putProtocol(Protocol protocol) {
+ protocolRepository.putProtocol(protocol);
+ }
+
+ @Override
+ public Protocol getProtocol(Utf8Array name) {
+ return protocolRepository.getProtocol(name.toString());
+ }
+
+ public Protocol getProtocol(Utf8String name) {
+ return getProtocol((Utf8Array)name);
+ }
+
+ @Override
+ public void deliverReply(Reply reply, ReplyHandler handler) {
+ msn.deliverReply(reply, handler);
+ }
+
+ @Override
+ public void setupRouting(RoutingSpec spec) {
+ Map<String, RoutingTable> tables = new HashMap<String, RoutingTable>();
+ for (int i = 0, len = spec.getNumTables(); i < len; ++i) {
+ RoutingTableSpec table = spec.getTable(i);
+ String name = table.getProtocol();
+ if (!protocolRepository.hasProtocol(name)) {
+ log.log(LogLevel.INFO, "Protocol '" + name + "' is not supported, ignoring routing table.");
+ continue;
+ }
+ tables.put(name, new RoutingTable(table));
+ }
+ tablesRef.set(tables);
+ protocolRepository.clearPolicyCache();
+ }
+
+ /**
+ * <p>Returns the resender that is running within this message bus.</p>
+ *
+ * @return The resender.
+ */
+ public Resender getResender() {
+ return resender;
+ }
+
+ /**
+ * <p>Returns the number of messages received that have not been replied to
+ * yet.</p>
+ *
+ * @return The pending count.
+ */
+ public synchronized int getPendingCount() {
+ return pendingCount;
+ }
+
+ /**
+ * <p>Returns the size of messages received that have not been replied to
+ * yet.</p>
+ *
+ * @return The pending size.
+ */
+ public synchronized int getPendingSize() {
+ return pendingSize;
+ }
+
+ /**
+ * <p>Sets the maximum number of messages that can be received without being
+ * replied to yet.</p>
+ *
+ * @param maxCount The max count.
+ */
+ public void setMaxPendingCount(int maxCount) {
+ maxPendingCount = maxCount;
+ }
+
+ /**
+ * Gets maximum number of messages that can be received without being
+ * replied to yet.
+ */
+ public int getMaxPendingCount() {
+ return maxPendingCount;
+ }
+
+ /**
+ * <p>Sets the maximum size of messages that can be received without being
+ * replied to yet.</p>
+ *
+ * @param maxSize The max size.
+ */
+ public void setMaxPendingSize(int maxSize) {
+ maxPendingSize = maxSize;
+ }
+
+ /**
+ * Gets maximum combined size of messages that can be received without
+ * being replied to yet.
+ */
+ public int getMaxPendingSize() {
+ return maxPendingSize;
+ }
+
+ /**
+ * <p>Returns a named routing table, may return null.</p>
+ *
+ * @param name The name of the routing table to return.
+ * @return The routing table object.
+ */
+ public RoutingTable getRoutingTable(String name) {
+ Map<String, RoutingTable> tables = tablesRef.get();
+ if (tables == null) {
+ return null;
+ }
+ return tables.get(name);
+ }
+ /**
+ * <p>Returns a named routing table, may return null.</p>
+ *
+ * @param name The name of the routing table to return.
+ * @return The routing table object.
+ */
+ public RoutingTable getRoutingTable(Utf8String name) {
+
+ return getRoutingTable(name.toString());
+ }
+
+ /**
+ * <p>Returns a routing policy that corresponds to the argument protocol
+ * name, policy name and policy parameter. This will cache reuse all
+ * policies as soon as they are first requested.</p>
+ *
+ * @param protocolName The name of the protocol to invoke {@link Protocol#createPolicy(String,String)} on.
+ * @param policyName The name of the routing policy to retrieve.
+ * @param policyParam The parameter for the routing policy to retrieve.
+ * @return A corresponding routing policy, or null.
+ */
+ public RoutingPolicy getRoutingPolicy(String protocolName, String policyName, String policyParam) {
+ return protocolRepository.getRoutingPolicy(protocolName, policyName, policyParam);
+ }
+
+ /**
+ * <p>Returns a routing policy that corresponds to the argument protocol
+ * name, policy name and policy parameter. This will cache reuse all
+ * policies as soon as they are first requested.</p>
+ *
+ * @param protocolName The name of the protocol to invoke {@link Protocol#createPolicy(String,String)} on.
+ * @param policyName The name of the routing policy to retrieve.
+ * @param policyParam The parameter for the routing policy to retrieve.
+ * @return A corresponding routing policy, or null.
+ */
+ public RoutingPolicy getRoutingPolicy(Utf8String protocolName, String policyName, String policyParam) {
+ return protocolRepository.getRoutingPolicy(protocolName.toString(), policyName, policyParam);
+ }
+
+ /**
+ * <p>Returns the connection spec string for the network layer of this
+ * message bus. This is merely a proxy of the same function in the network
+ * layer.</p>
+ *
+ * @return The connection string.
+ */
+ public String getConnectionSpec() {
+ return net.getConnectionSpec();
+ }
+
+ /**
+ * <p>Constructs and schedules a Reply containing an error to the handler of the given Message.</p>
+ *
+ * @param msg The message to reply to.
+ * @param errCode The code of the error to set.
+ * @param errMsg The message of the error to set.
+ */
+ private void deliverError(Message msg, int errCode, String errMsg) {
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(errCode, errMsg));
+ deliverReply(reply, reply.popHandler());
+ }
+
+ /**
+ * <p>Implements a task for running the resender in the messenger
+ * thread. This task acts as a proxy for the resender, allowing the task to
+ * be deleted without affecting the resender itself.</p>
+ */
+ private static class ResenderTask implements Messenger.Task {
+
+ final Resender resender;
+
+ ResenderTask(Resender resender) {
+ this.resender = resender;
+ }
+
+ public void destroy() {
+ // empty
+ }
+
+ public void run() {
+ resender.resendScheduled();
+ }
+
+ }
+}
+
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/MessageBusParams.java b/messagebus/src/main/java/com/yahoo/messagebus/MessageBusParams.java
new file mode 100755
index 00000000000..767f2cd0ec1
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/MessageBusParams.java
@@ -0,0 +1,146 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.messagebus.routing.RetryPolicy;
+import com.yahoo.messagebus.routing.RetryTransientErrorsPolicy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus} constructor, all parameters are held by this
+ * class. This class has reasonable default values for each parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MessageBusParams {
+
+ private final List<Protocol> protocols = new ArrayList<Protocol>();
+ private RetryPolicy retryPolicy;
+ private int maxPendingCount;
+ private int maxPendingSize;
+
+ /**
+ * Constructs a new instance of this parameter object with default values for all members.
+ */
+ public MessageBusParams() {
+ retryPolicy = new RetryTransientErrorsPolicy();
+ maxPendingCount = 1024;
+ maxPendingSize = 128 * 1024 * 1024;
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param params The object to copy.
+ */
+ public MessageBusParams(MessageBusParams params) {
+ protocols.addAll(params.protocols);
+ retryPolicy = params.retryPolicy;
+ maxPendingCount = params.maxPendingCount;
+ maxPendingSize = params.maxPendingSize;
+ }
+
+ /**
+ * Returns the retry policy for the resender.
+ *
+ * @return The policy.
+ */
+ public RetryPolicy getRetryPolicy() {
+ return retryPolicy;
+ }
+
+ /**
+ * Sets the retry policy for the resender.
+ *
+ * @param retryPolicy The policy to set.
+ * @return This, to allow chaining.
+ */
+ public MessageBusParams setRetryPolicy(RetryPolicy retryPolicy) {
+ this.retryPolicy = retryPolicy;
+ return this;
+ }
+
+ /**
+ * Adds a new protocol to this.
+ *
+ * @param protocol The protocol to add.
+ * @return This, to allow chaining.
+ */
+ public MessageBusParams addProtocol(Protocol protocol) {
+ protocols.add(protocol);
+ return this;
+ }
+
+ /**
+ * Registers multiple protocols with this by calling {@link #addProtocol(Protocol)} multiple times.
+ *
+ * @param protocols The protocols to register.
+ * @return This, to allow chaining.
+ */
+ public MessageBusParams addProtocols(List<Protocol> protocols) {
+ for (Protocol protocol : protocols) {
+ addProtocol(protocol);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the number of protocols that are contained in this.
+ *
+ * @return The number of protocols.
+ */
+ public int getNumProtocols() {
+ return protocols.size();
+ }
+
+ /**
+ * Returns the protocol at the given index.
+ *
+ * @param i The index of the protocol to return.
+ * @return The protocol object.
+ */
+ public Protocol getProtocol(int i) {
+ return protocols.get(i);
+ }
+
+ /**
+ * Returns the maximum number of pending messages.
+ *
+ * @return The count limit.
+ */
+ public int getMaxPendingCount() {
+ return maxPendingCount;
+ }
+
+ /**
+ * Sets the maximum number of allowed pending messages.
+ *
+ * @param maxCount The count limit to set.
+ * @return This, to allow chaining.
+ */
+ public MessageBusParams setMaxPendingCount(int maxCount) {
+ this.maxPendingCount = maxCount;
+ return this;
+ }
+
+ /**
+ * Returns the maximum number of bytes allowed for pending messages.
+ *
+ * @return The size limit.
+ */
+ public int getMaxPendingSize() {
+ return maxPendingSize;
+ }
+
+ /**
+ * Sets the maximum number of bytes allowed for pending messages.
+ *
+ * @param maxSize The size limit to set.
+ * @return This, to allow chaining.
+ */
+ public MessageBusParams setMaxPendingSize(int maxSize) {
+ this.maxPendingSize = maxSize;
+ return this;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/MessageHandler.java b/messagebus/src/main/java/com/yahoo/messagebus/MessageHandler.java
new file mode 100755
index 00000000000..3b59611c258
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/MessageHandler.java
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * All classes that wants to handle messages that move through the messagebus need to implement this interface.
+ * As opposed to the {@link ReplyHandler} which handles replies as they return from the receiver to the sender, this
+ * interface is intended for handling messages as they travel from the sender to the receiver.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface MessageHandler {
+
+ /**
+ * This function is called when a message arrives.
+ *
+ * @param message The message that arrived.
+ */
+ public void handleMessage(Message message);
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Messenger.java b/messagebus/src/main/java/com/yahoo/messagebus/Messenger.java
new file mode 100755
index 00000000000..17d4b06b8bf
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Messenger.java
@@ -0,0 +1,304 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.log.LogLevel;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+/**
+ * <p>This class implements a single thread that is able to process arbitrary
+ * tasks. Tasks are enqueued using the synchronized {@link #enqueue(Task)}
+ * method, and are run in the order they were enqueued.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Messenger implements Runnable {
+
+ private static final Logger log = Logger.getLogger(Messenger.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final List<Task> children = new ArrayList<>();
+ private final Queue<Task> queue = new ArrayDeque<>();
+ private final Thread thread = new Thread(this, "Messenger");
+
+ public Messenger() {
+ thread.setDaemon(true);
+ }
+
+ /**
+ * <p>Adds a recurrent task to this that is to be run for every iteration of
+ * the main loop. This task must be very light-weight as to not block the
+ * messenger. Note that this method is NOT thread-safe, so it should NOT be
+ * used after calling {@link #start()}.</p>
+ *
+ * @param task The task to add.
+ */
+ public void addRecurrentTask(final Task task) {
+ children.add(task);
+ }
+
+ /**
+ * <p>Starts the internal thread. This must be done AFTER all recurrent
+ * tasks have been added.</p>
+ *
+ * @see #addRecurrentTask(Task)
+ */
+ public void start() {
+ thread.start();
+ }
+
+ /**
+ * <p>Convenience method to post a {@link Task} that delivers a {@link
+ * Message} to a {@link MessageHandler} to the queue of tasks to be
+ * executed.</p>
+ *
+ * @param msg The message to send.
+ * @param handler The handler to send to.
+ */
+ public void deliverMessage(final Message msg, final MessageHandler handler) {
+ enqueue(new MessageTask(msg, handler));
+ }
+
+ /**
+ * <p>Convenience method to post a {@link Task} that delivers a {@link
+ * Reply} to a {@link ReplyHandler} to the queue of tasks to be
+ * executed.</p>
+ *
+ * @param reply The reply to return.
+ * @param handler The handler to return to.
+ */
+ public void deliverReply(final Reply reply, final ReplyHandler handler) {
+ enqueue(new ReplyTask(reply, handler));
+ }
+
+ /**
+ * <p>Enqueues the given task in the list of tasks that this worker is to
+ * process. If this thread has been destroyed previously, this method
+ * invokes {@link Messenger.Task#destroy()}.</p>
+ *
+ * @param task The task to enqueue.
+ */
+ public void enqueue(final Task task) {
+ if (destroyed.get()) {
+ task.destroy();
+ return;
+ }
+ synchronized (this) {
+ queue.offer(task);
+ if (queue.size() == 1) {
+ notify();
+ }
+ }
+ }
+
+ /**
+ * <p>Handshakes with the internal thread. If this method is called using
+ * the messenger thread, this will deadlock.</p>
+ */
+ public void sync() {
+ if (Thread.currentThread() == thread) {
+ return; // no need to wait for self
+ }
+ final SyncTask task = new SyncTask();
+ enqueue(task);
+ task.await();
+ }
+
+ /**
+ * <p>Sets the destroyed flag to true. The very first time this method is
+ * called, it cleans up all its dependencies. Even if you retain a
+ * reference to this object, all of its content is allowed to be garbage
+ * collected.</p>
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ boolean done = false;
+ enqueue(Terminate.INSTANCE);
+ if (!destroyed.getAndSet(true)) {
+ try {
+ synchronized (this) {
+ while (!queue.isEmpty()) {
+ wait();
+ }
+ }
+ thread.join();
+ } catch (final InterruptedException e) {
+ // ignore
+ }
+ done = true;
+ }
+ return done;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ Task task = null;
+ synchronized (this) {
+ if (queue.isEmpty()) {
+ try {
+ wait(100);
+ } catch (final InterruptedException e) {
+ continue;
+ }
+ }
+ if (queue.size() > 0) {
+ task = queue.poll();
+ }
+ }
+ if (task == Terminate.INSTANCE) {
+ break;
+ }
+ if (task != null) {
+ try {
+ task.run();
+ } catch (final Exception e) {
+ log.log(LogLevel.ERROR, "An exception was thrown while running " + task.getClass().getName(), e);
+ }
+ try {
+ task.destroy();
+ } catch (final Exception e) {
+ log.warning("An exception was thrown while destroying " + task.getClass().getName() + ": " +
+ e.toString());
+ log.warning("Someone, somewhere might have to wait indefinetly for something.");
+ }
+ }
+ for (final Task child : children) {
+ child.run();
+ }
+ }
+ for (final Task child : children) {
+ child.destroy();
+ }
+ synchronized (this) {
+ while (!queue.isEmpty()) {
+ final Task task = queue.poll();
+ task.destroy();
+ }
+ notify();
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "Messenger destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * <p>Defines the required interface for tasks to be posted to this
+ * worker.</p>
+ */
+ public interface Task {
+
+ /**
+ * <p>This method is called when being executed.</p>
+ */
+ public void run();
+
+ /**
+ * <p>This method is called for all tasks, even if {@link #run()} was
+ * never called.</p>
+ */
+ public void destroy();
+ }
+
+ private static class MessageTask implements Task {
+
+ final MessageHandler handler;
+ Message msg;
+
+ MessageTask(final Message msg, final MessageHandler handler) {
+ this.msg = msg;
+ this.handler = handler;
+ }
+
+ @Override
+ public void run() {
+ final Message msg = this.msg;
+ this.msg = null;
+ handler.handleMessage(msg);
+ }
+
+ @Override
+ public void destroy() {
+ if (msg != null) {
+ msg.discard();
+ }
+ }
+ }
+
+ private static class ReplyTask implements Task {
+
+ final ReplyHandler handler;
+ Reply reply;
+
+ ReplyTask(final Reply reply, final ReplyHandler handler) {
+ this.reply = reply;
+ this.handler = handler;
+ }
+
+ @Override
+ public void run() {
+ final Reply reply = this.reply;
+ this.reply = null;
+ handler.handleReply(reply);
+ }
+
+ @Override
+ public void destroy() {
+ if (reply != null) {
+ reply.discard();
+ }
+ }
+ }
+
+ private static class SyncTask implements Task {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void run() {
+ // empty
+ }
+
+ @Override
+ public void destroy() {
+ latch.countDown();
+ }
+
+ public void await() {
+ try {
+ latch.await();
+ } catch (final InterruptedException e) {
+ // ignore
+ }
+ }
+ }
+
+ private static class Terminate implements Task {
+
+ static final Terminate INSTANCE = new Terminate();
+
+ @Override
+ public void run() {
+ // empty
+ }
+
+ @Override
+ public void destroy() {
+ // empty
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Protocol.java b/messagebus/src/main/java/com/yahoo/messagebus/Protocol.java
new file mode 100644
index 00000000000..4e96c9af959
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Protocol.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.component.Version;
+import com.yahoo.messagebus.metrics.MetricSet;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+
+/**
+ * Interface implemented by the concrete application message protocol.
+ *
+ * @author <a href="bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface Protocol {
+
+ /**
+ * Returns a global unique name for this protocol.
+ *
+ * @return The name.
+ */
+ public String getName();
+
+ /**
+ * Encodes the protocol specific data of a routable into a byte array.
+ *
+ * @param version The version to encode for.
+ * @param routable The routable to encode.
+ * @return The encoded data.
+ */
+ public byte[] encode(Version version, Routable routable);
+
+ /**
+ * Decodes the protocol specific data into a routable of the correct type.
+ *
+ * @param version The version of the serialized routable.
+ * @param payload The payload to decode from.
+ * @return The decoded routable.
+ */
+ public Routable decode(Version version, byte[] payload);
+
+ /**
+ * Create a policy of the named type with the named param passed to the constructor of that policy.
+ *
+ * @param name The name of the policy to create.
+ * @param param The parameter to that policy's constructor.
+ * @return The created policy.
+ */
+ public RoutingPolicy createPolicy(String name, String param);
+
+ /**
+ * Returns the metrics associated with this protocol.
+ */
+ MetricSet getMetrics();
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/ProtocolRepository.java b/messagebus/src/main/java/com/yahoo/messagebus/ProtocolRepository.java
new file mode 100755
index 00000000000..949a8486fb7
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/ProtocolRepository.java
@@ -0,0 +1,109 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.concurrent.CopyOnWriteHashMap;
+import com.yahoo.log.LogLevel;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+import com.yahoo.text.Utf8String;
+
+import java.util.logging.Logger;
+
+/**
+ * Implements a thread-safe repository for protocols and their routing policies. This manages an internal cache of
+ * routing policies so that similarly referenced policy directives share the same instance of a policy.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ProtocolRepository {
+
+ private static final Logger log = Logger.getLogger(ProtocolRepository.class.getName());
+ private final CopyOnWriteHashMap<String, Protocol> protocols = new CopyOnWriteHashMap<>();
+ private final CopyOnWriteHashMap<String, RoutingPolicy> routingPolicyCache = new CopyOnWriteHashMap<>();
+
+ /**
+ * Registers a protocol with this repository. This will overwrite any protocol that was registered earlier that has
+ * the same name. If this method detects a protocol replacement, it will clear its internal routing policy cache.
+ *
+ * @param protocol The protocol to register.
+ */
+ public void putProtocol(Protocol protocol) {
+ if (protocols.put(protocol.getName(), protocol) != null) {
+ routingPolicyCache.clear();
+ }
+ }
+
+ /**
+ * Returns whether or not this repository contains a protocol with the given name. Given the concurrent nature of
+ * things, one should not invoke this method followed by {@link #getProtocol(String)} and expect the return value to
+ * be non-null. Instead just get the protocol and compare it to null.
+ *
+ * @param name The name to check for.
+ * @return True if the named protocol is registered.
+ */
+ public boolean hasProtocol(String name) {
+ return protocols.containsKey(name);
+ }
+
+ /**
+ * Returns the protocol whose name matches the given argument. This method will return null if no such protocol has
+ * been registered.
+ *
+ * @param name The name of the protocol to return.
+ * @return The protocol registered, or null.
+ */
+ public Protocol getProtocol(String name) {
+ return protocols.get(name);
+ }
+
+ /**
+ * Creates and returns a routing policy that matches the given arguments. If a routing policy has been created
+ * previously using the exact same parameters, this method will returned that cached instance instead of creating
+ * another. Not that when you replace a protocol using {@link #putProtocol(Protocol)} the policy cache is cleared.
+ *
+ * @param protocolName The name of the protocol whose routing policy to create.
+ * @param policyName The name of the routing policy to create.
+ * @param policyParam The parameter to pass to the routing policy constructor.
+ * @return The created routing policy.
+ */
+ public RoutingPolicy getRoutingPolicy(String protocolName, String policyName, String policyParam) {
+ String cacheKey = protocolName + "." + policyName + "." + policyParam;
+ RoutingPolicy ret = routingPolicyCache.get(cacheKey);
+ if (ret != null) {
+ return ret;
+ }
+ synchronized (this) {
+ Protocol protocol = getProtocol(protocolName);
+ if (protocol == null) {
+ log.log(LogLevel.ERROR, "Protocol '" + protocolName + "' not supported.");
+ return null;
+ }
+ try {
+ ret = protocol.createPolicy(policyName, policyParam);
+ } catch (RuntimeException e) {
+ log.log(LogLevel.ERROR, "Protcol '" + protocolName + "' threw an exception: " + e.getMessage(), e);
+ return null;
+ }
+ if (ret == null) {
+ log.log(LogLevel.ERROR, "Protocol '" + protocolName + "' failed to create routing policy '" + policyName +
+ "' with parameter '" + policyParam + "'.");
+ return null;
+ }
+ routingPolicyCache.put(cacheKey, ret);
+ }
+ return ret;
+ }
+
+ public final RoutingPolicy getRoutingPolicy(Utf8String protocolName, String policyName, String policyParam) {
+ return getRoutingPolicy(protocolName.toString(), policyName, policyParam);
+ }
+
+ /**
+ * Clears the internal cache of routing policies.
+ */
+ public synchronized void clearPolicyCache() {
+ for (RoutingPolicy policy : routingPolicyCache.values()) {
+ policy.destroy();
+ }
+ routingPolicyCache.clear();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/RPCMessageBus.java b/messagebus/src/main/java/com/yahoo/messagebus/RPCMessageBus.java
new file mode 100644
index 00000000000..d767e197b11
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/RPCMessageBus.java
@@ -0,0 +1,112 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.log.LogLevel;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetwork;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+/**
+ * The RPCMessageBus class wraps a MessageBus with an RPCNetwork and handles reconfiguration. Please note that according
+ * to the object shutdown order, you must shut down all sessions before shutting down this object.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RPCMessageBus {
+
+ private static final Logger log = Logger.getLogger(RPCMessageBus.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final MessageBus mbus;
+ private final RPCNetwork net;
+ private final ConfigAgent configAgent;
+
+ /**
+ * Constructs a new instance of this class.
+ *
+ * @param mbusParams A complete set of message bus parameters.
+ * @param rpcParams A complete set of network parameters.
+ * @param routingCfgId The config id for message bus routing specs.
+ */
+ public RPCMessageBus(MessageBusParams mbusParams, RPCNetworkParams rpcParams, String routingCfgId) {
+ net = new RPCNetwork(rpcParams);
+ mbus = new MessageBus(net, mbusParams);
+ configAgent = new ConfigAgent(routingCfgId != null ? routingCfgId : "client", mbus);
+ configAgent.subscribe();
+ }
+
+ /**
+ * This constructor requires an array of protocols that it is to support, as well as the host application's config
+ * identifier. That identifier is necessary so that all created sessions can be uniquely identified on the network.
+ *
+ * @param protocols An array of known protocols.
+ * @param rpcParams A complete set of network parameters.
+ * @param routingCfgId The config id for message bus routing specs.
+ */
+ public RPCMessageBus(List<Protocol> protocols, RPCNetworkParams rpcParams, String routingCfgId) {
+ this(new MessageBusParams().addProtocols(protocols), rpcParams, routingCfgId);
+ }
+
+ /**
+ * This constructor requires a single protocol that it is to support, as well as the host application's config
+ * identifier.
+ *
+ * @param protocol An instance of the known protocol.
+ * @param configId The host application's config id. This will be used to resolve the service name prefix used when
+ * registering with the slobrok. Using null here is allowed, but will not allow intermediate- or
+ * destination sessions to be routed to.
+ */
+ public RPCMessageBus(Protocol protocol, String configId) {
+ this(Arrays.asList(protocol), new RPCNetworkParams().setIdentity(new Identity(configId)), null);
+ }
+
+ // Overrides Object.
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "RPCMessageBus destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Sets the destroyed flag to true. The very first time this method is called, it cleans up all its dependencies.
+ * Even if you retain a reference to this object, all of its content is allowed to be garbage collected.
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (!destroyed.getAndSet(true)) {
+ configAgent.shutdown();
+ mbus.destroy();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the contained message bus object.
+ *
+ * @return Message bus.
+ */
+ public MessageBus getMessageBus() {
+ return mbus;
+ }
+
+ /**
+ * Returns the contained rpc network object.
+ *
+ * @return RPC network.
+ */
+ public RPCNetwork getRPCNetwork() {
+ return net;
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/RateThrottlingPolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/RateThrottlingPolicy.java
new file mode 100644
index 00000000000..a5f2004080e
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/RateThrottlingPolicy.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.concurrent.SystemTimer;
+import com.yahoo.concurrent.Timer;
+
+import java.util.logging.Logger;
+
+/**
+ * Throttling policy that throttles sending based on a desired rate. It will
+ * block messages if the current rate is higher than desired, but otherwise will
+ * respect the static throttle policy's maximum window size.
+ *
+ * Rate is measured from at most the last 60 seconds.
+ */
+public class RateThrottlingPolicy extends StaticThrottlePolicy {
+
+ public static final Logger log = Logger.getLogger(RateThrottlingPolicy.class.getName());
+
+ long PERIOD = 1000;
+ double desiredRate;
+
+ double allotted = 0.0;
+ long currentPeriod = 0;
+
+ Timer timer;
+
+ public RateThrottlingPolicy(double desiredRate) {
+ this(desiredRate, SystemTimer.INSTANCE);
+ }
+
+ public RateThrottlingPolicy(double desiredRate, Timer timer) {
+ this.desiredRate = desiredRate;
+ this.timer = timer;
+ currentPeriod = timer.milliTime() / PERIOD;
+ }
+
+ public boolean canSend(Message msg, int pendingCount) {
+ if (!super.canSend(msg, pendingCount)) {
+ return false;
+ }
+
+ long period = timer.milliTime() / PERIOD;
+
+ while (currentPeriod < period) {
+ if (allotted > 0) {
+ allotted = 0.0;
+ }
+
+ allotted = allotted + PERIOD * desiredRate / 1000;
+ currentPeriod++;
+ }
+
+ if (allotted > 0.0) {
+ allotted -= 1;
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Reply.java b/messagebus/src/main/java/com/yahoo/messagebus/Reply.java
new file mode 100644
index 00000000000..c43f84714de
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Reply.java
@@ -0,0 +1,141 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * <p>A reply is a response to a message that has been sent throught the messagebus. No reply will ever exist without a
+ * corresponding message. There are no error-replies defined, as errors can instead piggyback any reply by the {@link
+ * #errors} member variable.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class Reply extends Routable {
+
+ private double retryDelay = -1.0;
+ private Message msg = null;
+ private List<Error> errors = new ArrayList<>();
+
+ @Override
+ public void swapState(Routable rhs) {
+ super.swapState(rhs);
+ if (rhs instanceof Reply) {
+ Reply reply = (Reply)rhs;
+
+ double retryDelay = this.retryDelay;
+ this.retryDelay = reply.retryDelay;
+ reply.retryDelay = retryDelay;
+
+ Message msg = this.msg;
+ this.msg = reply.msg;
+ reply.msg = msg;
+
+ List<Error> errors = this.errors;
+ this.errors = reply.errors;
+ reply.errors = errors;
+ }
+ }
+
+ /**
+ * <p>Returns the message to which this is a reply.</p>
+ *
+ * @return The message.
+ */
+ public Message getMessage() {
+ return msg;
+ }
+
+ /**
+ * <p>Sets the message to which this is a reply. Although it might seem very bogus to allow such an accessor, it is
+ * necessary since we allow an empty constructor.</p>
+ *
+ * @param msg The message to which this is a reply.
+ */
+ public void setMessage(Message msg) {
+ this.msg = msg;
+ }
+
+ /**
+ * <p>Returns whether or not this reply contains any errors.</p>
+ *
+ * @return True if there are errors, false otherwise.
+ */
+ public boolean hasErrors() {
+ return errors.size() > 0;
+ }
+
+ /**
+ * <p>Returns whether or not this reply contains any fatal errors.</p>
+ *
+ * @return True if it contains fatal errors.
+ */
+ public boolean hasFatalErrors() {
+ for (Error error : errors) {
+ if (error.getCode() >= ErrorCode.FATAL_ERROR) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * <p>Returns the error at the given position.</p>
+ *
+ * @param i The index of the error to return.
+ * @return The error at the given index.
+ */
+ public Error getError(int i) {
+ return errors.get(i);
+ }
+
+ /**
+ * <p>Returns the number of errors that this reply contains.</p>
+ *
+ * @return The number of replies.
+ */
+ public int getNumErrors() {
+ return errors.size();
+ }
+
+ /**
+ * <p>Add an error to this reply. This method will also trace the error as long as there is any tracing
+ * enabled.</p>
+ *
+ * @param error The error object to add.
+ */
+ public void addError(Error error) {
+ errors.add(error);
+ getTrace().trace(TraceLevel.ERROR, error.toString());
+ }
+
+ /**
+ * <p>Returns the retry request of this reply. This can be set using {@link #setRetryDelay} and is an instruction to
+ * the resender logic of message bus on how to perform the retry. If this value is anything other than a negative
+ * number, it instructs the resender to disregard all configured resending attributes and instead act according to
+ * this value.</p>
+ *
+ * @return The retry request.
+ */
+ public double getRetryDelay() {
+ return retryDelay;
+ }
+
+ /**
+ * <p>Sets the retry delay request of this reply. If this is a negative number, it will use the defaults configured
+ * in the source session.</p>
+ *
+ * @param retryDelay The retry request.
+ */
+ public void setRetryDelay(double retryDelay) {
+ this.retryDelay = retryDelay;
+ }
+
+ /**
+ * Retrieves a (read only) stream of the errors in this reply
+ */
+ public Stream<Error> getErrors() {
+ return errors.stream();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/ReplyHandler.java b/messagebus/src/main/java/com/yahoo/messagebus/ReplyHandler.java
new file mode 100644
index 00000000000..7b51348d4aa
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/ReplyHandler.java
@@ -0,0 +1,20 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * All classes that wants to handle replies that move through the messagebus need to implement this interface. As
+ * opposed to the {@link MessageHandler} which handles messages as they travel from the sender to the receiver, this
+ * interface is intended for handling replies as they return from the receiver to the sender.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface ReplyHandler {
+
+ /**
+ * This function is called when a reply arrives.
+ *
+ * @param reply The reply that arrived.
+ */
+ void handleReply(Reply reply);
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Result.java b/messagebus/src/main/java/com/yahoo/messagebus/Result.java
new file mode 100644
index 00000000000..095398f6b5d
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Result.java
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * <p>Information on the outcome of <i>initiating</i> a send or forward on a session.
+ * The result will tell if the send/forward was accepted or not. If it was accepted,
+ * an (asynchroneous) reply is guaranteed to be delivered at some later time.
+ * If it was not accepted, a <i>transient error</i> has occured. In that case,
+ * {@link #getError} can be used to access the exact error.</p>
+ *
+ * <p>This class is <b>immutable</b>.
+ *
+ * @author <a href="bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Result {
+
+ public static final Result ACCEPTED = new Result();
+ private final Error error;
+
+ /**
+ * The default constructor is private so that the only error-results can be new'ed by
+ * the user. All accepted results should use the public "accepted" constant.
+ */
+ private Result() {
+ error = null;
+ }
+
+ /**
+ * This constructor assigns a given error to the member variable such that this result
+ * becomes unaccepted with a descriptive error.
+ *
+ * @param error The error to assign to this result.
+ */
+ public Result(Error error) {
+ this.error = error;
+ }
+
+ /**
+ * This constructor is a convencience function to allow simpler instantiation of a result that contains an error.
+ * It does nothing but proxy the {@link #Result(Error)} function with a new instance of {@link Error}.
+ *
+ * @param code The numerical code of the error.
+ * @param message The description of the error.
+ */
+ public Result(int code, String message) {
+ this(new Error(code, message));
+ }
+
+ /**
+ * Returns whether this message was accepted.
+ * If it was accepted, a Reply is guaranteed to be produced for this message
+ * at some later time. If it was not accepted, getError can be called to
+ * investigate why.
+ *
+ * @return true if this message was accepted, false otherwise
+ */
+ public boolean isAccepted() {
+ return error == null;
+ }
+
+ /**
+ * The error resulting from this send/forward if the message was not accepted.
+ *
+ * @return The error is not accepcted, null if accepted.
+ */
+ public Error getError() {
+ return error;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Routable.java b/messagebus/src/main/java/com/yahoo/messagebus/Routable.java
new file mode 100755
index 00000000000..cbc8ce8f2d2
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Routable.java
@@ -0,0 +1,126 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.text.Utf8String;
+
+/**
+ * Superclass for objects that can be either explicitly (Message) or implicitly (Reply) routed. Note that protocol
+ * implementors should never subclass this directly, but rather through the {@link Message} and {@link Reply} classes.
+ *
+ * A routable can be regarded as a protocol-defined value with additional message bus related state. The state is what
+ * differentiates two Routables that carry the same value. This includes the application context attached to the
+ * routable and the {@link CallStack} used to track the path of the routable within messagebus. When a routable is
+ * copied (if the protocol supports it) only the value part is copied. The state must be explicitly transfered by
+ * invoking the {@link #swapState(Routable)} method. That method is used to transfer the state from a message to the
+ * corresponding reply, or to a different message if the application decides to replace it.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class Routable {
+
+ private final CallStack callStack = new CallStack();
+ private final Trace trace = new Trace();
+ private Object context = null;
+
+ /**
+ * Discards this routable. Invoking this prevents the auto-generation of replies if you later discard the routable.
+ * This is a required step to ensure safe shutdown if you need destroy a message bus instance while there are still
+ * messages and replies alive in your application.
+ */
+ public void discard() {
+ context = null;
+ callStack.clear();
+ trace.clear();
+ }
+
+ /**
+ * Swaps the state that makes this routable unique to another routable. The state is what identifies a routable for
+ * message bus, so only one message can ever have the same state. This function must be called explicitly when
+ * cloning and copying messages.
+ *
+ * @param rhs The routable to swap state with.
+ */
+ public void swapState(Routable rhs) {
+ Object context = this.context;
+ this.context = rhs.context;
+ rhs.context = context;
+
+ callStack.swap(rhs.getCallStack());
+ trace.swap(rhs.getTrace());
+ }
+
+ /**
+ * Pushes the given reply handler onto the call stack of this routable, also storing the current context.
+ *
+ * @param handler The handler to push.
+ */
+ public void pushHandler(ReplyHandler handler) {
+ callStack.push(handler, context);
+ }
+
+ /**
+ * <p>This is a convenience method for calling {@link CallStack#pop(Routable)} on the {@link CallStack} of this
+ * Routable. It equals calling <tt>routable.getCallStack().pop(routable)</tt>.</p>
+ *
+ * @return The handler that was popped.
+ * @see CallStack#pop(Routable)
+ */
+ public ReplyHandler popHandler() {
+ return callStack.pop(this);
+ }
+
+ /**
+ * Return the context of this routable.
+ *
+ * @return The context.
+ */
+ public Object getContext() {
+ return context;
+ }
+
+ /**
+ * Set a new context for this routable. Please note that the context is <u>not</u> something that is passed along a
+ * message, it is simply a user context for the handler currently manipulating a message. When the corresponding
+ * reply reaches the registered reply handler, its content will be the same as that of the outgoing message. More
+ * technically, this context is contained in the callstack of a routable.
+ *
+ * @param context The new context.
+ */
+ public void setContext(Object context) {
+ this.context = context;
+ }
+
+ /**
+ * Return the callstack of this routable.
+ *
+ * @return The callstack.
+ */
+ public CallStack getCallStack() {
+ return callStack;
+ }
+
+ /**
+ * Returns the trace object of this routable.
+ *
+ * @return The trace object.
+ */
+ public Trace getTrace() {
+ return trace;
+ }
+
+ /**
+ * Return the name of the protocol that defines this routable. This must be implemented by all inheriting classes,
+ * and should then return the result of {@link com.yahoo.messagebus.Protocol#getName} of its protocol.
+ *
+ * @return The name of the protocol defining this message.
+ */
+ public abstract Utf8String getProtocol();
+
+ /**
+ * Obtain the type of this routable. The id '0' is reserved for the EmptyReply class. Other ids must be defined by
+ * the application protocol.
+ *
+ * @return The message type.
+ */
+ public abstract int getType();
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/SendProxy.java b/messagebus/src/main/java/com/yahoo/messagebus/SendProxy.java
new file mode 100644
index 00000000000..fe894427a13
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/SendProxy.java
@@ -0,0 +1,93 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.concurrent.SystemTimer;
+import com.yahoo.messagebus.metrics.RouteMetricSet;
+import com.yahoo.messagebus.network.Network;
+import com.yahoo.messagebus.routing.Resender;
+import com.yahoo.messagebus.routing.RoutingNode;
+import com.yahoo.log.LogLevel;
+
+import java.util.logging.Logger;
+
+/**
+ * This class owns a message that is being sent by message bus. Once a reply is received, the message is attached to it
+ * and returned to the application. This also implements the discard policy of {@link RoutingNode}.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SendProxy implements MessageHandler, ReplyHandler {
+
+ private static final Logger log = Logger.getLogger(SendProxy.class.getName());
+ private final MessageBus mbus;
+ private final Network net;
+ private final Resender resender;
+ private Message msg = null;
+ private boolean logTrace = false;
+ private long sendTime = 0;
+
+ /**
+ * Constructs a new instance of this class to maintain sending of a single message.
+ *
+ * @param mbus The message bus that owns this.
+ * @param net The network layer to transmit through.
+ * @param resender The resender to use.
+ */
+ public SendProxy(MessageBus mbus, Network net, Resender resender) {
+ this.mbus = mbus;
+ this.net = net;
+ this.resender = resender;
+ sendTime = SystemTimer.INSTANCE.milliTime();
+ }
+
+ public void handleMessage(Message msg) {
+ Trace trace = msg.getTrace();
+ if (trace.getLevel() == 0) {
+ if (log.isLoggable(LogLevel.SPAM)) {
+ trace.setLevel(9);
+ logTrace = true;
+ } else if (log.isLoggable(LogLevel.DEBUG)) {
+ trace.setLevel(6);
+ logTrace = true;
+ }
+ }
+ this.msg = msg;
+ RoutingNode root = new RoutingNode(mbus, net, resender, this, msg);
+ root.send();
+ }
+
+ public void handleReply(Reply reply) {
+ if (reply == null) {
+ msg.discard();
+ } else {
+ Trace trace = msg.getTrace();
+ if (logTrace) {
+ if (reply.hasErrors()) {
+ log.log(LogLevel.DEBUG, "Trace for reply with error(s):\n" + reply.getTrace());
+ } else if (log.isLoggable(LogLevel.SPAM)) {
+ log.log(LogLevel.SPAM, "Trace for reply:\n" + reply.getTrace());
+ }
+ Trace empty = new Trace();
+ trace.swap(empty);
+ } else if (trace.getLevel() > 0) {
+ trace.getRoot().addChild(reply.getTrace().getRoot());
+ trace.getRoot().normalize();
+ }
+ reply.swapState(msg);
+ reply.setMessage(msg);
+
+ if (msg.getRoute() != null) {
+ RouteMetricSet metrics = mbus.getMetrics().getRouteMetrics(msg.getRoute());
+ for (int i = 0; i < reply.getNumErrors(); i++) {
+ metrics.addFailure(reply.getError(i));
+ }
+ if (reply.getNumErrors() == 0) {
+ metrics.latency.addValue(msg.getTimeReceived() - sendTime);
+ }
+ }
+
+ ReplyHandler handler = reply.popHandler();
+ handler.handleReply(reply);
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Sequencer.java b/messagebus/src/main/java/com/yahoo/messagebus/Sequencer.java
new file mode 100644
index 00000000000..6f90bb8c994
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Sequencer.java
@@ -0,0 +1,155 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Sequencing is implemented as a message handler that is configured in a source session in that session's chain of
+ * linked message handlers. Each message that carries a sequencing id is queued in an internal list of messages for that
+ * id, and messages are only sent when they are at the front of their list. When a reply arrives, the current front of
+ * the list is removed and the next message, if any, is sent.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Sequencer implements MessageHandler, ReplyHandler {
+
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final MessageHandler sender;
+ private final Map<Long, Queue<Message>> seqMap = new HashMap<Long, Queue<Message>>();
+
+ /**
+ * Constructs a new sequencer on top of the given async sender.
+ *
+ * @param sender The underlying sender.
+ */
+ public Sequencer(MessageHandler sender) {
+ this.sender = sender;
+ }
+
+ /**
+ * Sets the destroyed flag to true. The very first time this method is called, it cleans up all its dependencies.
+ * Even if you retain a reference to this object, all of its content is allowed to be garbage collected.
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (!destroyed.getAndSet(true)) {
+ synchronized (this) {
+ for (Queue<Message> queue : seqMap.values()) {
+ if (queue != null) {
+ for (Message msg : queue) {
+ msg.discard();
+ }
+ }
+ }
+ seqMap.clear();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Filter a message against the current sequencing state. If this method returns true, the message has been cleared
+ * for sending and its sequencing information has been added to the state. If this method returns false, it has been
+ * queued for later sending due to sequencing restrictions. This method also sets the sequence id as message
+ * context.
+ *
+ * @param msg The message to filter.
+ * @return True if the message was consumed.
+ */
+ private boolean filter(Message msg) {
+ long seqId = msg.getSequenceId();
+ msg.setContext(seqId);
+ synchronized (this) {
+ if (seqMap.containsKey(seqId)) {
+ Queue<Message> queue = seqMap.get(seqId);
+ if (queue == null) {
+ queue = new LinkedList<Message>();
+ seqMap.put(seqId, queue);
+ }
+ if (msg.getTrace().shouldTrace(TraceLevel.COMPONENT)) {
+ msg.getTrace().trace(TraceLevel.COMPONENT,
+ "Sequencer queued message with sequence id '" + seqId + "'.");
+ }
+ queue.add(msg);
+ return false;
+ }
+ seqMap.put(seqId, null);
+ }
+ return true;
+ }
+
+ /**
+ * Internal method for forwarding a sequenced message to the underlying sender.
+ *
+ * @param msg The message to forward.
+ */
+ private void sequencedSend(Message msg) {
+ if (msg.getTrace().shouldTrace(TraceLevel.COMPONENT)) {
+ msg.getTrace().trace(TraceLevel.COMPONENT,
+ "Sequencer sending message with sequence id '" + msg.getContext() + "'.");
+ }
+ msg.pushHandler(this);
+ sender.handleMessage(msg);
+ }
+
+ /**
+ * All messages pass through this handler when being sent by the owning source session. In case the message has no
+ * sequencing-id, it is simply passed through to the next handler in the chain. Sequenced messages are sent only if
+ * there is no queue for their id, otherwise they are queued.
+ *
+ * @param msg The message to send.
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ if (destroyed.get()) {
+ msg.discard();
+ return;
+ }
+ if (msg.hasSequenceId()) {
+ if (filter(msg)) {
+ sequencedSend(msg);
+ }
+ } else {
+ sender.handleMessage(msg); // unsequenced
+ }
+ }
+
+ /**
+ * Lookup the sequencing id of an incoming reply to pop the front of the corresponding queue, and then send the next
+ * message in line, if any.
+ *
+ * @param reply The reply received.
+ */
+ @Override
+ public void handleReply(Reply reply) {
+ if (destroyed.get()) {
+ reply.discard();
+ return;
+ }
+ long seqId = (Long)reply.getContext(); // non-sequenced messages do not enter here
+ if (reply.getTrace().shouldTrace(TraceLevel.COMPONENT)) {
+ reply.getTrace().trace(TraceLevel.COMPONENT,
+ "Sequencer received reply with sequence id '" + seqId + "'.");
+ }
+ Message msg = null;
+ synchronized (this) {
+ Queue<Message> queue = seqMap.get(seqId);
+ if (queue == null || queue.isEmpty()) {
+ seqMap.remove(seqId);
+ } else {
+ msg = queue.remove();
+ }
+ }
+ if (msg != null) {
+ sequencedSend(msg);
+ }
+ ReplyHandler handler = reply.popHandler();
+ handler.handleReply(reply);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/SourceSession.java b/messagebus/src/main/java/com/yahoo/messagebus/SourceSession.java
new file mode 100644
index 00000000000..bef6e37476c
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/SourceSession.java
@@ -0,0 +1,300 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.log.LogLevel;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingTable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+/**
+ * <p>A session supporting sending new messages.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class SourceSession implements ReplyHandler {
+
+ private static Logger log = Logger.getLogger(SourceSession.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final CountDownLatch done = new CountDownLatch(1);
+ private final Object lock = new Object();
+ private final MessageBus mbus;
+ private final Sequencer sequencer;
+ private final ReplyHandler replyHandler;
+ private final ThrottlePolicy throttlePolicy;
+ private volatile double timeout;
+ private volatile int pendingCount = 0;
+ private boolean closed = false;
+
+ /**
+ * <p>The default constructor requires values for all final member variables
+ * of this. It expects all arguments but the {@link SourceSessionParams} to
+ * be proper, so no checks are performed. The constructor is declared
+ * package private since only {@link MessageBus} is supposed to instantiate
+ * it.</p>
+ *
+ * @param mbus The message bus that created this instance.
+ * @param params A parameter object that holds configuration parameters.
+ */
+ SourceSession(MessageBus mbus, SourceSessionParams params) {
+ this.mbus = mbus;
+ sequencer = new Sequencer(mbus);
+ if (!params.hasReplyHandler()) {
+ throw new NullPointerException("Reply handler is null.");
+ }
+ replyHandler = params.getReplyHandler();
+ throttlePolicy = params.getThrottlePolicy();
+ timeout = params.getTimeout();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "SourceSession destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Sets the destroyed flag to true. The very first time this method is
+ * called, it cleans up all its dependencies. Even if you retain a
+ * reference to this object, all of its content is allowed to be garbage
+ * collected.
+ *
+ * @return true if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (destroyed.getAndSet(true)) {
+ return false;
+ }
+ synchronized (lock) {
+ closed = true;
+ }
+ sequencer.destroy();
+ mbus.sync();
+ return true;
+ }
+
+ /**
+ * Reject all new messages and wait until no messages are pending. Before
+ * returning, this method calls {@link #destroy()}.
+ */
+ public void close() {
+ synchronized (lock) {
+ closed = true;
+ }
+ if (pendingCount == 0) {
+ done.countDown();
+ }
+ try {
+ done.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ destroy();
+ }
+
+ /**
+ * <p>Sends a new message. Calling this immediately causes one of three
+ * possible results:</p>
+ * <ul><li>A result is returned indicating that the message is accepted. In
+ * this case, a reply to the message is guaranteed to be produced on this
+ * session within a timeout limit. That reply may indicate either success or
+ * failure.</li>
+ * <li>A result is returned indicating that the message is not
+ * accepted. This is a <i>transient failure</i>, retrying the same operation
+ * after some wait period should cause it to be accepted.</li>
+ * <li>An exception is thrown, indicating a non-transient error which is not
+ * expected to be fixed before some corrective action is taken.</li> </ul>
+ *
+ * <p>A source client should typically do some equivalent of:</p>
+ * <code>
+ * do {
+ * Result result = sourceSession.send(message);
+ * if (!result.isAccepted())
+ * // Do something else or wait a while
+ * } while (!result.isAccepted());
+ * </code>
+ *
+ * @param msg the message to send
+ * @return The result of <i>initiating</i> sending of this message.
+ */
+ public Result send(Message msg) {
+ msg.setTimeReceivedNow();
+ if (msg.getTimeRemaining() <= 0) {
+ msg.setTimeRemaining((long)(timeout * 1000));
+ }
+ synchronized (lock) {
+ if (closed) {
+ return new Result(ErrorCode.SEND_QUEUE_CLOSED,
+ "Source session is closed.");
+ }
+ if (throttlePolicy != null && !throttlePolicy.canSend(msg, pendingCount)) {
+ return new Result(ErrorCode.SEND_QUEUE_FULL,
+ "Too much pending data (" + pendingCount + " messages).");
+ }
+ msg.pushHandler(replyHandler);
+ if (throttlePolicy != null) {
+ throttlePolicy.processMessage(msg);
+ }
+ ++pendingCount;
+ }
+ if (msg.getTrace().shouldTrace(TraceLevel.COMPONENT)) {
+ msg.getTrace().trace(TraceLevel.COMPONENT,
+ "Source session accepted a " + msg.getApproxSize() + " byte message. " +
+ pendingCount + " message(s) now pending.");
+ }
+ msg.pushHandler(this);
+ sequencer.handleMessage(msg);
+ return Result.ACCEPTED;
+ }
+
+ /**
+ * <p>This is a blocking proxy to the {@link #send(Message)} method. This
+ * method blocks until the message is accepted by the send queue. Note that
+ * the message timeout does not activate by calling this method. This method
+ * will also return if this session is closed or the calling thread is
+ * interrupted.</p>
+ *
+ * @param msg The message to send.
+ * @return The result of initiating send.
+ * @throws InterruptedException Thrown if the calling thread is interrupted.
+ */
+ public Result sendBlocking(Message msg) throws InterruptedException {
+ while (true) {
+ Result res = send(msg);
+ if (res.isAccepted() || res.getError().getCode() != ErrorCode.SEND_QUEUE_FULL) {
+ return res;
+ }
+ synchronized (lock) {
+ while (!closed && !throttlePolicy.canSend(msg, pendingCount)) {
+ lock.wait(100);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleReply(Reply reply) {
+ if (destroyed.get()) {
+ reply.discard();
+ return;
+ }
+ boolean done;
+ synchronized (lock) {
+ --pendingCount;
+ if (throttlePolicy != null) {
+ throttlePolicy.processReply(reply);
+ }
+ done = (closed && pendingCount == 0);
+ lock.notifyAll();
+ }
+ if (reply.getTrace().shouldTrace(TraceLevel.COMPONENT)) {
+ reply.getTrace().trace(TraceLevel.COMPONENT,
+ "Source session received reply. " + pendingCount + " message(s) now pending.");
+ }
+ ReplyHandler handler = reply.popHandler();
+ handler.handleReply(reply);
+ if (done) {
+ this.done.countDown();
+ }
+ }
+
+ /**
+ * <p>This is a convenience function to assign a given route to the given
+ * message, and then pass it to the other {@link #send(Message)} method of
+ * this session.</p>
+ *
+ * @param msg The message to send.
+ * @param route The route to assign to the message.
+ * @return The immediate result of the attempt to send this message.
+ */
+ public Result send(Message msg, Route route) {
+ return send(msg.setRoute(route));
+ }
+
+ /**
+ * <p>This is a convenience method to call {@link
+ * #send(Message,String,boolean)} with a <code>false</code> value for the
+ * 'parseIfNotFound' parameter.</p>
+ *
+ * @param msg The message to send.
+ * @param routeName The route to assign to the message.
+ * @return The immediate result of the attempt to send this message.
+ */
+ public Result send(Message msg, String routeName) {
+ return send(msg, routeName, false);
+ }
+
+ /**
+ * <p>This is a convenience function to assign a named route to the given
+ * message, and then pass it to the other {@link #send(Message)} method of
+ * this session. If the route could not be found this methods returns with
+ * an appropriate error, unless the 'parseIfNotFound' argument is true. In
+ * that case, the route name is passed through to the Route factory method
+ * {@link Route#parse}.</p>
+ *
+ * @param msg The message to send.
+ * @param routeName The route to assign to the message.
+ * @param parseIfNotFound Whether or not to parse routeName as a route if
+ * it could not be found.
+ * @return The immediate result of the attempt to send this message.
+ */
+ public Result send(Message msg, String routeName, boolean parseIfNotFound) {
+ boolean found = false;
+ RoutingTable table = mbus.getRoutingTable(msg.getProtocol().toString());
+ if (table != null) {
+ Route route = table.getRoute(routeName);
+ if (route != null) {
+ msg.setRoute(new Route(route));
+ found = true;
+ } else if (!parseIfNotFound) {
+ return new Result(ErrorCode.ILLEGAL_ROUTE,
+ "Route '" + routeName + "' not found for protocol '" + msg.getProtocol() + "'.");
+ }
+ } else if (!parseIfNotFound) {
+ return new Result(ErrorCode.ILLEGAL_ROUTE,
+ "Protocol '" + msg.getProtocol() + "' has no routing table.");
+ }
+ if (!found) {
+ msg.setRoute(Route.parse(routeName));
+ }
+ return send(msg);
+ }
+
+ /**
+ * <p>Returns the reply handler of this session.</p>
+ *
+ * @return The reply handler.
+ */
+ public ReplyHandler getReplyHandler() {
+ return replyHandler;
+ }
+
+ /**
+ * <p>Returns the number of messages sent that have not been replied to
+ * yet.</p>
+ *
+ * @return The pending count.
+ */
+ public int getPendingCount() {
+ return pendingCount;
+ }
+
+ /**
+ * <p>Sets the number of seconds a message can be attempted sent until it
+ * times out.</p>
+ *
+ * @param timeout The numer of seconds allowed.
+ * @return This, to allow chaining.
+ */
+ public SourceSession setTimeout(double timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/SourceSessionParams.java b/messagebus/src/main/java/com/yahoo/messagebus/SourceSessionParams.java
new file mode 100644
index 00000000000..0a57e777dbd
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/SourceSessionParams.java
@@ -0,0 +1,104 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus#createSourceSession(ReplyHandler,
+ * SourceSessionParams)}, all parameters are held by this class. This class has reasonable default values for each
+ * parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SourceSessionParams {
+
+ private ReplyHandler replyHandler = null;
+ private ThrottlePolicy throttlePolicy = new DynamicThrottlePolicy();
+ private double timeout = 180.0;
+
+ /**
+ * Instantiates a parameter object with default values.
+ */
+ public SourceSessionParams() {
+ // empty
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param params The object to copy.
+ */
+ public SourceSessionParams(SourceSessionParams params) {
+ throttlePolicy = params.throttlePolicy;
+ timeout = params.timeout;
+ replyHandler = params.replyHandler;
+ }
+
+ /**
+ * Returns the policy to use for throttling output.
+ *
+ * @return The policy.
+ */
+ public ThrottlePolicy getThrottlePolicy() {
+ return throttlePolicy;
+ }
+
+ /**
+ * Sets the policy to use for throttling output.
+ *
+ * @param throttlePolicy The policy to set.
+ * @return This, to allow chaining.
+ */
+ public SourceSessionParams setThrottlePolicy(ThrottlePolicy throttlePolicy) {
+ this.throttlePolicy = throttlePolicy;
+ return this;
+ }
+
+ /**
+ * Returns the number of seconds a message can spend trying to succeed.
+ *
+ * @return The timeout in seconds.
+ */
+ public double getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Sets the number of seconds a message can be attempted sent until it times out. This is the maximum allowed time
+ * for any message bus operation.
+ *
+ * @param timeout The numer of seconds allowed.
+ * @return This, to allow chaining.
+ */
+ public SourceSessionParams setTimeout(double timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+
+ /**
+ * Returns whether or not a reply handler has been assigned to this.
+ *
+ * @return True if a handler is set.
+ */
+ boolean hasReplyHandler() {
+ return replyHandler != null;
+ }
+
+ /**
+ * Returns the handler to receive incoming replies.
+ *
+ * @return The handler.
+ */
+ public ReplyHandler getReplyHandler() {
+ return replyHandler;
+ }
+
+ /**
+ * Sets the handler to recive incoming replies.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ public SourceSessionParams setReplyHandler(ReplyHandler handler) {
+ replyHandler = handler;
+ return this;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/StaticThrottlePolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/StaticThrottlePolicy.java
new file mode 100644
index 00000000000..bb1ae9e69b3
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/StaticThrottlePolicy.java
@@ -0,0 +1,91 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * This is an implementatin of the {@link ThrottlePolicy} that offers static limits to the amount of pending data a
+ * {@link SourceSession} is allowed to have. You may choose to set a limit to the total number of pending messages (by
+ * way of {@link #setMaxPendingCount(int)}), the total size of pending messages (by way of {@link
+ * #setMaxPendingSize(long)}), or some combination thereof.
+ *
+ * <b>NOTE:</b> By context, "pending" is refering to the number of sent messages that have not been replied to yet.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class StaticThrottlePolicy implements ThrottlePolicy {
+
+ private int maxPendingCount = 0;
+ private long maxPendingSize = 0;
+ private long pendingSize = 0;
+
+ public boolean canSend(Message msg, int pendingCount) {
+ if (maxPendingCount > 0 && pendingCount >= maxPendingCount) {
+ return false;
+ }
+ if (maxPendingSize > 0 && pendingSize >= maxPendingSize) {
+ return false;
+ }
+ return true;
+ }
+
+ public void processMessage(Message msg) {
+ int size = msg.getApproxSize();
+ msg.setContext(size);
+ pendingSize += size;
+ }
+
+ public void processReply(Reply reply) {
+ int size = (Integer)reply.getContext();
+ pendingSize -= size;
+ }
+
+ /**
+ * Returns the maximum number of pending messages allowed.
+ *
+ * @return The max limit.
+ */
+ public int getMaxPendingCount() {
+ return maxPendingCount;
+ }
+
+ /**
+ * Sets the maximum number of pending messages allowed.
+ *
+ * @param maxCount The max count.
+ * @return This, to allow chaining.
+ */
+ public StaticThrottlePolicy setMaxPendingCount(int maxCount) {
+ maxPendingCount = maxCount;
+ return this;
+ }
+
+ /**
+ * Returns the maximum total size of pending messages allowed.
+ *
+ * @return The max limit.
+ */
+ public long getMaxPendingSize() {
+ return maxPendingSize;
+ }
+
+ /**
+ * Sets the maximum total size of pending messages allowed. This size is relative to the value returned by {@link
+ * com.yahoo.messagebus.Message#getApproxSize()}.
+ *
+ * @param maxSize The max size.
+ * @return This, to allow chaining.
+ */
+ public StaticThrottlePolicy setMaxPendingSize(long maxSize) {
+ maxPendingSize = maxSize;
+ return this;
+ }
+
+ /**
+ * Returns the total size of pending messages.
+ *
+ * @return The size.
+ */
+ public long getPendingSize() {
+ return pendingSize;
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/ThrottlePolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/ThrottlePolicy.java
new file mode 100644
index 00000000000..611694b0c62
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/ThrottlePolicy.java
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * An implementation of this interface is used by {@link SourceSession} to throttle output. Every message entering
+ * {@link SourceSession#send(Message)} needs to be accepted by this interface's {@link #canSend(Message, int)} method.
+ * All messages accepted are passed through the {@link #processMessage(Message)} method, and the corresponding replies
+ * are passed through the {@link #processReply(Reply)} method.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface ThrottlePolicy {
+
+ /**
+ * Returns whether or not the given message can be sent according to the current state of this policy.
+ *
+ * @param msg The message to evaluate.
+ * @param pendingCount The current number of pending messages.
+ * @return True to send the message.
+ */
+ public boolean canSend(Message msg, int pendingCount);
+
+ /**
+ * This method is called once for every message that was accepted by {@link #canSend(Message, int)} and sent.
+ *
+ * @param msg The message beint sent.
+ */
+ public void processMessage(Message msg);
+
+ /**
+ * This method is called once for every reply that is received.
+ *
+ * @param reply The reply received.
+ */
+ public void processReply(Reply reply);
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/Trace.java b/messagebus/src/main/java/com/yahoo/messagebus/Trace.java
new file mode 100755
index 00000000000..00db4963dea
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/Trace.java
@@ -0,0 +1,158 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import java.util.Date;
+
+/**
+ * A Trace object contains ad-hoc string notes organized in a strict-loose tree. A Trace object consists of a trace
+ * level indicating which trace notes should be included and a TraceTree object containing the tree structure and
+ * collecting the trace information. Tracing is used to collect debug information about a Routable traveling through the
+ * system. The trace level is in the range [0,9]. 0 means no tracing, and 9 means all tracing is enabled. A client that
+ * has the ability to trace information will have a predefined level attached to that information. If the level on the
+ * information is lower or equal to the level set in the Trace object, the information will be traced.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Trace {
+
+ private int level = 0;
+ private TraceNode root = new TraceNode();
+
+ /**
+ * Create an empty trace with level set to 0 (no tracing)
+ */
+ public Trace() {
+ // empty
+ }
+
+ /**
+ * Create an empty trace with given level.
+ *
+ * @param level Level to set.
+ */
+ public Trace(int level) {
+ this.level = level;
+ }
+
+ /**
+ * Remove all trace information and set the trace level to 0.
+ *
+ * @return This, to allow chaining.
+ */
+ public Trace clear() {
+ level = 0;
+ root = new TraceNode();
+ return this;
+ }
+
+ /**
+ * Swap the internals of this with another.
+ *
+ * @param other The trace to swap internals with.
+ * @return This, to allow chaining.
+ */
+ public Trace swap(Trace other) {
+ int level = this.level;
+ this.level = other.level;
+ other.level = level;
+
+ TraceNode root = this.root;
+ this.root = other.root;
+ other.root = root;
+
+ return this;
+ }
+
+ /**
+ * Set the trace level. 0 means no tracing, 9 means enable all tracing.
+ *
+ * @param level The level to set.
+ * @return This, to allow chaining.
+ */
+ public Trace setLevel(int level) {
+ this.level = Math.min(Math.max(level, 0), 9);
+ return this;
+ }
+
+ /**
+ * Returns the trace level.
+ *
+ * @return The trace level.
+ */
+ public int getLevel() {
+ return level;
+ }
+
+ /**
+ * Check if information with the given level should be traced. This method is added to allow clients to check if
+ * something should be traced before spending time building up the trace information itself.
+ *
+ * @param level The trace level to test.
+ * @return True if tracing is enabled for the given level, false otherwise.
+ */
+ public boolean shouldTrace(int level) {
+ return level <= this.level;
+ }
+
+ /**
+ * Add the given note to the trace information if tracing is enabled for the given level.
+ *
+ * @param level The trace level of the note.
+ * @param note The note to add.
+ * @return True if the note was added to the trace information, false otherwise.
+ */
+ public boolean trace(int level, String note) {
+ return trace(level, note, true);
+ }
+
+ /**
+ * Add the given note to the trace information if tracing is enabled for the given level. If the addTime parameter
+ * is true, then the note is prefixed with the current time. This is the default behaviour when ommiting this
+ * parameter.
+ *
+ * @param level The trace level of the note.
+ * @param note The note to add.
+ * @param addTime Whether or not to prefix note with a timestamp.
+ * @return True if the note was added to the trace information, false otherwise.
+ */
+ public boolean trace(int level, String note, boolean addTime) {
+ if (!shouldTrace(level)) {
+ return false;
+ }
+ if (addTime) {
+ String timeString = Long.toString(System.currentTimeMillis());
+ StringBuilder buf = new StringBuilder();
+ buf.append("[");
+ int len = timeString.length();
+ // something wrong. handle it by using the input long as a string.
+ if (len < 3) {
+ buf.append(timeString);
+ } else {
+ buf.append(timeString.substring(0, len - 3));
+ buf.append('.');
+ buf.append(timeString.substring(len - 3));
+ }
+ buf.append("] ");
+ buf.append(note);
+ root.addChild(buf.toString());
+ } else {
+ root.addChild(note);
+ }
+ return true;
+ }
+
+ /**
+ * Returns the root of the trace tree.
+ *
+ * @return The root.
+ */
+ public TraceNode getRoot() {
+ return root;
+ }
+
+ // Overrides Object.
+ @Override
+ public String toString() {
+ return root.toString(31337);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/TraceLevel.java b/messagebus/src/main/java/com/yahoo/messagebus/TraceLevel.java
new file mode 100755
index 00000000000..994eb17b434
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/TraceLevel.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * This class defines the {@link Trace} levels used by message bus.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class TraceLevel {
+
+ /**
+ * Traces whenever an Error is added to a Reply.
+ */
+ public static final int ERROR = 1;
+
+ /**
+ * Traces sending and receiving messages and replies on network level.
+ */
+ public static final int SEND_RECEIVE = 4;
+
+ /**
+ * Traces splitting messages and merging replies.
+ */
+ public static final int SPLIT_MERGE = 5;
+
+ /**
+ * Traces information about which internal components are processing a routable.
+ */
+ public static final int COMPONENT = 6;
+} \ No newline at end of file
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/TraceNode.java b/messagebus/src/main/java/com/yahoo/messagebus/TraceNode.java
new file mode 100755
index 00000000000..d7f24432496
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/TraceNode.java
@@ -0,0 +1,473 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.log.LogLevel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * This class contains the actual trace information of a {@link Trace} object. A trace node can be encoded to, and
+ * decoded from a string representation to allow transport across the network. Each node contains a list of children, a
+ * strictness flag and an optional note. The child list is what forms the trace tree, the strictness flag dictates
+ * whether or not the ordering of the children is important, and the note is the actual traced data.
+ *
+ * The most important feature to notice is the {@link #normalize()} method that will compact, sort and 'rootify' the
+ * trace tree so that trees become well-formed (and can be compared for equality).
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TraceNode implements Comparable<TraceNode> {
+
+ private static final Logger log = Logger.getLogger(TraceNode.class.getName());
+ private TraceNode parent = null;
+ private boolean strict = true;
+ private String note = null;
+ private List<TraceNode> children = new ArrayList<TraceNode>();
+
+ /**
+ * Create an empty trace tree.
+ */
+ public TraceNode() {
+ // empty
+ }
+
+ /**
+ * Create a leaf node with the given note.
+ *
+ * @param note The note to assign to this.
+ */
+ private TraceNode(String note) {
+ this.note = note;
+ }
+
+ /**
+ * Create a trace tree which is a copy of another.
+ *
+ * @param rhs The tree to copy.
+ */
+ TraceNode(TraceNode rhs) {
+ strict = rhs.strict;
+ note = rhs.note;
+ addChildren(rhs.children);
+ }
+
+ /**
+ * Swap the internals of this tree with another.
+ *
+ * @param other The tree to swap internals with.
+ * @return This, to allow chaining.
+ */
+ public TraceNode swap(TraceNode other) {
+ TraceNode parent = this.parent;
+ this.parent = other.parent;
+ other.parent = parent;
+
+ boolean strict = this.strict;
+ this.strict = other.strict;
+ other.strict = strict;
+
+ String note = this.note;
+ this.note = other.note;
+ other.note = note;
+
+ List<TraceNode> children = this.children;
+ this.children = other.children;
+ for (TraceNode child : this.children) {
+ child.parent = this;
+ }
+ other.children = children;
+ for (TraceNode child : other.children) {
+ child.parent = other;
+ }
+
+ return this;
+ }
+
+ /**
+ * Remove all trace information from this tree.
+ *
+ * @return This, to allow chaining.
+ */
+ public TraceNode clear() {
+ parent = null;
+ strict = true;
+ note = null;
+ children.clear();
+ return this;
+ }
+
+ /**
+ * Sort non-strict children recursively down the tree.
+ *
+ * @return This, to allow chaining.
+ */
+ public TraceNode sort() {
+ if (!isLeaf()) {
+ for (TraceNode child : children) {
+ child.sort();
+ }
+ if (!isStrict()) {
+ Collections.sort(children);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public int compareTo(TraceNode rhs) {
+ if (isLeaf() || rhs.isLeaf()) {
+ if (isLeaf() && rhs.isLeaf()) {
+ return note.compareTo(rhs.getNote());
+ } else {
+ return isLeaf() ? -1 : 1;
+ }
+ }
+ if (children.size() != rhs.children.size()) {
+ return children.size() < rhs.children.size() ? -1 : 1;
+ }
+ for (int i = 0; i < children.size(); ++i) {
+ int cmp = children.get(i).compareTo(rhs.children.get(i));
+ if (cmp != 0) {
+ return cmp;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Compact this tree. This will reduce the height of this tree as much as possible without removing information
+ * stored in it.
+ *
+ * @return This, to allow chaining.
+ */
+ public TraceNode compact() {
+ if (isLeaf()) {
+ return this;
+ }
+ List<TraceNode> tmp = this.children;
+ this.children = new ArrayList<TraceNode>();
+ for (TraceNode child : tmp) {
+ child.compact();
+ if (child.isEmpty()) {
+ // ignore
+ } else if (child.isLeaf()) {
+ addChild(child);
+ } else if (strict == child.strict) {
+ addChildren(child.children);
+ } else if (child.getNumChildren() == 1) {
+ TraceNode grandChild = child.getChild(0);
+ if (grandChild.isEmpty()) {
+ // ignore
+ } else if (grandChild.isLeaf() || strict != grandChild.strict) {
+ addChild(grandChild);
+ } else {
+ addChildren(grandChild.children);
+ }
+ } else {
+ addChild(child);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Normalize this tree. This will transform all equivalent trees into the same form. Note that this will also
+ * perform an implicit compaction of the tree.
+ *
+ * @return This, to allow chaining.
+ */
+ public TraceNode normalize() {
+ compact();
+ sort();
+ if (note != null || !strict) {
+ TraceNode child = new TraceNode();
+ child.swap(this);
+ addChild(child);
+ strict = true;
+ }
+ return this;
+ }
+
+ /**
+ * Check whether or not this is a root node.
+ *
+ * @return True if this has no parent.
+ */
+ public boolean isRoot() {
+ return parent == null;
+ }
+
+ /**
+ * Check whether or not this is a leaf node.
+ *
+ * @return True if this has no children.
+ */
+ public boolean isLeaf() {
+ return children.isEmpty();
+ }
+
+ /**
+ * Check whether or not this node is empty, i.e. it has no note and no children.
+ *
+ * @return True if this node is empty.
+ */
+ public boolean isEmpty() {
+ return note == null && children.isEmpty();
+ }
+
+ /**
+ * Check whether or not the children of this node are strictly ordered.
+ *
+ * @return True if this node is strict.
+ */
+ public boolean isStrict() {
+ return strict;
+ }
+
+ /**
+ * Sets whether or not the children of this node are strictly ordered.
+ *
+ * @param strict True to order children strictly.
+ * @return This, to allow chaining.
+ */
+ public TraceNode setStrict(boolean strict) {
+ this.strict = strict;
+ return this;
+ }
+
+ /**
+ * Returns whether or not a note is assigned to this node.
+ *
+ * @return True if a note is assigned.
+ */
+ public boolean hasNote() {
+ return note != null;
+ }
+
+ /**
+ * Returns the note assigned to this node.
+ *
+ * @return The note.
+ */
+ public String getNote() {
+ return note;
+ }
+
+ /**
+ * Returns the number of child nodes of this.
+ *
+ * @return The number of children.
+ */
+ public int getNumChildren() {
+ return children.size();
+ }
+
+ /**
+ * Returns the child trace node at the given index.
+ *
+ * @param i The index of the child to return.
+ * @return The child at the given index.
+ */
+ public TraceNode getChild(int i) {
+ return children.get(i);
+ }
+
+ /**
+ * Convenience method to add a child node containing a note to this.
+ *
+ * @param note The note to assign to the child.
+ * @return This, to allow chaining.
+ */
+ public TraceNode addChild(String note) {
+ return addChild(new TraceNode(note));
+ }
+
+ /**
+ * Adds a child node to this.
+ *
+ * @param child The child to add.
+ * @return This, to allow chaining.
+ */
+ public TraceNode addChild(TraceNode child) {
+ if (note != null) {
+ throw new IllegalStateException("Nodes with notes are leaf nodes, you can not add children to it.");
+ }
+ TraceNode node = new TraceNode(child);
+ node.parent = this;
+ children.add(node);
+ return this;
+ }
+
+ /**
+ * Adds a list of child nodes to this.
+ *
+ * @param children The children to add.
+ * @return This, to allow chaining.
+ */
+ public TraceNode addChildren(List<TraceNode> children) {
+ for (TraceNode child : children) {
+ addChild(child);
+ }
+ return this;
+ }
+
+ // Overrides Object.
+ @Override
+ public String toString() {
+ return toString(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Generates a non-parseable, human-readable string representation
+ * of this trace node.
+ *
+ * @return generated string
+ * @param limit soft limit for maximum string size
+ **/
+ public String toString(int limit) {
+ StringBuilder out = new StringBuilder();
+ if (!writeString(out, "", limit)) {
+ out.append("...\n");
+ }
+ return out.toString();
+ }
+
+ /**
+ * Writes a non-parseable, human-readable string representation of
+ * this trace node to the given string builder using the given
+ * indent string for every written line.
+ *
+ * @return false if written string was capped
+ * @param ret The string builder to write to.
+ * @param indent The indent to use.
+ * @param limit soft limit for maximum string size
+ */
+ private boolean writeString(StringBuilder ret, String indent, int limit) {
+ if (ret.length() >= limit) {
+ return false;
+ }
+ if (note != null) {
+ ret.append(indent).append(note).append("\n");
+ } else {
+ String name = isStrict() ? "trace" : "fork";
+ ret.append(indent).append("<").append(name).append(">\n");
+ for (TraceNode child : children) {
+ if (!child.writeString(ret, indent + " ", limit)) {
+ return false;
+ }
+ }
+ if (ret.length() >= limit) {
+ return false;
+ }
+ ret.append(indent).append("</").append(name).append(">\n");
+ }
+ return true;
+ }
+
+ /**
+ * Returns a parseable (using {@link #decode(String)}) string representation of this trace node.
+ *
+ * @return A string representation of this tree.
+ */
+ public String encode() {
+ StringBuilder ret = new StringBuilder();
+ encode(ret);
+ return ret.toString();
+ }
+
+ /**
+ * Writes a parseable string representation of this trace node to the given string builder.
+ *
+ * @param ret The string builder to write to.
+ */
+ private void encode(StringBuilder ret) {
+ if (note != null) {
+ ret.append("[");
+ for (int i = 0, len = note.length(); i < len; ++i) {
+ char c = note.charAt(i);
+ if (c == '\\' || c == ']') {
+ ret.append('\\');
+ }
+ ret.append(note.charAt(i));
+ }
+ ret.append("]");
+ } else {
+ ret.append(strict ? "(" : "{");
+ for (TraceNode child : children) {
+ child.encode(ret);
+ }
+ ret.append(strict ? ")" : "}");
+ }
+ }
+
+ /**
+ * Build a trace tree from the given string representation (possibly encoded using {@link #encode()}).
+ *
+ * @param str The string to parse.
+ * @return The corresponding trace tree, or an empty node if parsing failed.
+ */
+ public static TraceNode decode(String str) {
+ if (str == null || str.isEmpty()) {
+ return new TraceNode();
+ }
+ TraceNode proxy = new TraceNode();
+ TraceNode node = proxy;
+ StringBuilder note = null;
+ boolean inEscape = false;
+ for (int i = 0, len = str.length(); i < len; ++i) {
+ char c = str.charAt(i);
+ if (note != null) {
+ if (inEscape) {
+ note.append(c);
+ inEscape = false;
+ } else if (c == '\\') {
+ inEscape = true;
+ } else if (c == ']') {
+ node.addChild(note.toString());
+ note = null;
+ } else {
+ note.append(c);
+ }
+ } else {
+ if (c == '[') {
+ note = new StringBuilder();
+ } else if (c == '(' || c == '{') {
+ node.addChild(new TraceNode());
+ node = node.getChild(node.getNumChildren() - 1);
+ node.setStrict(c == '(');
+ } else if (c == ')' || c == '}') {
+ if (node == null) {
+ log.log(LogLevel.WARNING, "Unexpected closing brace in trace '" + str + "' at position " + i + ".");
+ return new TraceNode();
+ }
+ if (node.isStrict() != (c == ')')) {
+ log.log(LogLevel.WARNING, "Mismatched closing brace in trace '" + str + "' at position " + i + ".");
+ return new TraceNode();
+ }
+ node = node.parent;
+ }
+ }
+ }
+ if (note != null) {
+ log.log(LogLevel.WARNING, "Unterminated note in trace '" + str + "'.");
+ return new TraceNode();
+ }
+ if (node != proxy) {
+ log.log(LogLevel.WARNING, "Missing closing brace in trace '" + str + "'.");
+ return new TraceNode();
+ }
+ if (proxy.getNumChildren() == 0) {
+ log.log(LogLevel.WARNING, "No nodes found in trace '" + str + "'.");
+ return new TraceNode();
+ }
+ if (proxy.getNumChildren() != 1) {
+ return proxy; // best-effort recovery from malformed input
+ }
+ TraceNode ret = proxy.children.remove(0);
+ ret.parent = null;
+ return ret;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/AverageMetric.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/AverageMetric.java
new file mode 100644
index 00000000000..96339a2f703
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/AverageMetric.java
@@ -0,0 +1,52 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import com.yahoo.text.XMLWriter;
+import com.yahoo.text.Utf8String;
+
+/**
+ * @author thomasg
+ */
+public class AverageMetric extends Metric {
+ double sum = 0;
+ double min = 0;
+ double max = 0;
+ int count = 0;
+
+
+ public AverageMetric(String name, MetricSet owner) {
+ super(name);
+ owner.addMetric(this);
+ }
+
+ public void addValue(double value) {
+ sum += value;
+ count++;
+
+ if (min == 0 || value < min) {
+ min = value;
+ }
+ if (max == 0 || value > max) {
+ max = value;
+ }
+
+ }
+
+ static private final Utf8String attrValue = new Utf8String("value");
+ static private final Utf8String attrCount = new Utf8String("count");
+ static private final Utf8String attrMin = new Utf8String("min");
+ static private final Utf8String attrMax = new Utf8String("max");
+
+ @Override
+ public void toXML(XMLWriter writer) {
+ renderXmlName(writer);
+
+ if (count > 0) {
+ writer.attribute(attrValue, (sum / count));
+ writer.attribute(attrCount, count);
+ writer.attribute(attrMin, min);
+ writer.attribute(attrMax, max);
+ }
+ writer.closeTag();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/CountMetric.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/CountMetric.java
new file mode 100644
index 00000000000..2c2f21ddc20
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/CountMetric.java
@@ -0,0 +1,18 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author thomasg
+ */
+public class CountMetric extends NumberMetric<AtomicLong> {
+ public CountMetric(String name, MetricSet owner) {
+ super(name, new AtomicLong(0), owner);
+ }
+
+ public void inc(long increment) {
+ get().addAndGet(increment);
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/MessageBusMetricSet.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/MessageBusMetricSet.java
new file mode 100644
index 00000000000..28ef51d8a29
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/MessageBusMetricSet.java
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import com.yahoo.concurrent.CopyOnWriteHashMap;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.messagebus.routing.Route;
+
+/**
+ * @author thomasg
+ */
+public class MessageBusMetricSet extends MetricSet {
+ public MetricSet protocols = new MetricSet("protocols");
+
+ private final CopyOnWriteHashMap<String, RouteMetricSet> routeMetrics = new CopyOnWriteHashMap<String, RouteMetricSet>();
+
+ public MessageBusMetricSet() {
+ super("messagebus");
+ addMetric(protocols);
+ }
+
+ public RouteMetricSet getRouteMetrics(Route r) {
+ String route = r.toString();
+ RouteMetricSet metric = routeMetrics.get(route);
+ if (metric == null) {
+ synchronized (routeMetrics) {
+ metric = routeMetrics.get(route);
+ if (metric == null) {
+ metric = new RouteMetricSet(route);
+ addMetric(metric);
+ routeMetrics.put(route, metric);
+ }
+ }
+ }
+
+ return metric;
+ }
+
+ public void updateMetrics(Reply reply, Route r) {
+
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/Metric.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/Metric.java
new file mode 100644
index 00000000000..ac7ac1aa6cc
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/Metric.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import com.yahoo.text.XMLWriter;
+import com.yahoo.text.Utf8String;
+
+import java.io.Writer;
+
+/**
+ * @author thomasg
+ */
+public abstract class Metric {
+ String name;
+ String xmlTagName = null;
+
+ public Metric(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toHTML() {
+ return toString();
+ }
+
+ public String getXmlTagName() {
+ return xmlTagName;
+ }
+
+ public void setXmlTagName(String newName) {
+ xmlTagName = newName;
+ }
+
+ static private final Utf8String attrName = new Utf8String("name");
+
+ public void renderXmlName(XMLWriter writer) {
+ if (xmlTagName != null) {
+ writer.openTag(xmlTagName);
+ writer.attribute(attrName, name);
+ } else {
+ writer.openTag(name);
+ }
+ }
+
+ public abstract void toXML(XMLWriter writer);
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/MetricSet.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/MetricSet.java
new file mode 100644
index 00000000000..adca0d1c67f
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/MetricSet.java
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import com.yahoo.text.XMLWriter;
+
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author thomasg
+ */
+public class MetricSet extends Metric {
+ private List<Metric> metrics = new ArrayList<Metric>();
+
+ public MetricSet(String name) {
+ super(name);
+ }
+
+ public void addMetric(Metric m) {
+ metrics.add(m);
+ }
+
+ public List<Metric> getMetrics() {
+ return Collections.unmodifiableList(metrics);
+ }
+
+ public String toHTML() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<ul>\n");
+ for (Metric m : metrics) {
+ builder.append("<li>\n").append(m.toHTML()).append("\n</li>");
+ }
+ builder.append("\n</ul>\n");
+ return builder.toString();
+ }
+
+ public void toXML(XMLWriter xmlWriter) {
+ renderXmlName(xmlWriter);
+
+ for (Metric m : metrics) {
+ m.toXML(xmlWriter);
+ }
+
+ xmlWriter.closeTag();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/NumberMetric.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/NumberMetric.java
new file mode 100644
index 00000000000..f9269a9e53d
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/NumberMetric.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import com.yahoo.text.XMLWriter;
+import com.yahoo.text.Utf8String;
+
+/**
+ * @author thomasg
+ */
+public abstract class NumberMetric<V extends Number> extends Metric {
+ private V value;
+
+ public NumberMetric(String name, V v, MetricSet owner) {
+ super(name);
+ value = v;
+ owner.addMetric(this);
+ }
+
+ public V get() {
+ return value;
+ }
+
+ public void set(V value) {
+ this.value = value;
+ }
+
+ public String toString() {
+ return value.toString();
+ }
+
+ static private final Utf8String attrValue = new Utf8String("value");
+
+ public void toXML(XMLWriter writer) {
+ renderXmlName(writer);
+ writer.attribute(attrValue, value);
+ writer.closeTag();
+ }
+
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/RouteMetricSet.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/RouteMetricSet.java
new file mode 100644
index 00000000000..ba00fb6f578
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/RouteMetricSet.java
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import com.yahoo.messagebus.ErrorCode;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author thomasg
+ */
+public class RouteMetricSet extends MetricSet {
+ public MetricSet allErrors = new MetricSet("errors");
+ public MetricSet failures = new MetricSet("failures");
+ public AverageMetric latency = new AverageMetric("latency", this);
+
+ private Map<Integer, CountMetric> errorMap = new HashMap<Integer, CountMetric>();
+
+ RouteMetricSet(String route) {
+ super(route);
+ setXmlTagName("messages");
+ addMetric(allErrors);
+ addMetric(failures);
+ }
+
+ public void addError(com.yahoo.messagebus.Error e) {
+ CountMetric metric = errorMap.get(e.getCode());
+ if (metric == null) {
+ metric = new CountMetric(ErrorCode.getName(e.getCode()), allErrors);
+ metric.setXmlTagName("error");
+ errorMap.put(e.getCode(), metric);
+ }
+ metric.inc(1);
+ }
+
+ public void addFailure(com.yahoo.messagebus.Error e) {
+ CountMetric metric = errorMap.get(e.getCode());
+ if (metric == null) {
+ metric = new CountMetric(ErrorCode.getName(e.getCode()), failures);
+ metric.setXmlTagName("failure");
+ errorMap.put(e.getCode(), metric);
+ }
+ metric.inc(1);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/ValueMetric.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/ValueMetric.java
new file mode 100644
index 00000000000..f8a55661961
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/ValueMetric.java
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.metrics;
+
+import java.io.Writer;
+
+/**
+ * @author thomasg
+ */
+public class ValueMetric<V extends Number> extends NumberMetric<V> {
+
+ public ValueMetric(String name, V v, MetricSet owner) {
+ super(name, v, owner);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/metrics/package-info.java b/messagebus/src/main/java/com/yahoo/messagebus/metrics/package-info.java
new file mode 100644
index 00000000000..0562757fd9d
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/metrics/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.messagebus.metrics;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java b/messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java
new file mode 100644
index 00000000000..52b3d824019
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network;
+
+import com.yahoo.log.LogLevel;
+import com.yahoo.net.LinuxInetAddress;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.logging.Logger;
+
+/**
+ * This class encapsulates the identity of the application that uses this instance of message bus. This identity
+ * contains a servicePrefix identifier, which is the configuration id of the current servicePrefix, and the canonical
+ * host name of the host running this.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Identity {
+
+ private static final Logger log = Logger.getLogger(Identity.class.getName());
+ private final String hostname;
+ private final String servicePrefix;
+
+ /**
+ * The default constructor requires a configuration identifier that it will use to subscribe to this message bus'
+ * identity. This identity is necessary so that the network layer is able to identify self for registration with
+ * Slobrok.
+ *
+ * @param configId The config identifier for the application.
+ */
+ public Identity(String configId) {
+ InetAddress addr;
+ try {
+ addr = LinuxInetAddress.getLocalHost();
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ if (addr instanceof Inet6Address) {
+ log.log(LogLevel.WARNING, "Local host resolved to IPv6 address '" + addr.getHostAddress() +
+ "', this might be problematic.");
+ }
+ hostname = addr.getCanonicalHostName();
+ servicePrefix = configId;
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param identity The object to copy.
+ */
+ public Identity(Identity identity) {
+ hostname = identity.hostname;
+ servicePrefix = identity.servicePrefix;
+ }
+
+ /**
+ * Returns the hostname for this. This is the network name of the host on which this identity exists. It is
+ * retrieved on creation by InetAddress.getLocalHost().getCanonicalHostName().
+ *
+ * @return The canonical host name.
+ */
+ public String getHostname() {
+ return hostname;
+ }
+
+ /**
+ * Returns the service prefix for this. This is what is prefixed to every session that is created on this identity's
+ * message bus before registered in the naming service.
+ *
+ * @return The service prefix.
+ */
+ public String getServicePrefix() {
+ return servicePrefix;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/Network.java b/messagebus/src/main/java/com/yahoo/messagebus/network/Network.java
new file mode 100644
index 00000000000..cd3b3286778
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/Network.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network;
+
+import com.yahoo.jrt.slobrok.api.IMirror;
+import com.yahoo.messagebus.Message;
+import com.yahoo.messagebus.routing.RoutingNode;
+
+import java.util.List;
+
+/**
+ * This interface separates the low-level network implementation from the rest of messagebus. The methods defined in
+ * this interface is intended to be invoked by MessageBus and not by the application.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public interface Network {
+
+ /**
+ * Waits for at most the given number of seconds for all dependencies to become ready.
+ *
+ * @param seconds The timeout.
+ * @return True if ready.
+ */
+ public boolean waitUntilReady(double seconds);
+
+ /**
+ * Attach the network layer to the given owner
+ *
+ * @param owner owner of the network
+ */
+ public void attach(NetworkOwner owner);
+
+ /**
+ * Register a session name with the network layer. This will make the session visible to other nodes.
+ *
+ * @param session the session name
+ */
+ public void registerSession(String session);
+
+ /**
+ * Unregister a session name with the network layer. This will make the session unavailable for other nodes.
+ *
+ * @param session session name
+ */
+ public void unregisterSession(String session);
+
+ /**
+ * Resolves the service address of the recipient referenced by the given routing node. If a recipient can not be
+ * resolved, this method tags the node with an error. If this method succeeds, you need to invoke {@link
+ * #freeServiceAddress(RoutingNode)} once you are done with the service address.
+ *
+ * @param recipient The node whose service address to allocate.
+ * @return True if a service address was allocated.
+ */
+ public boolean allocServiceAddress(RoutingNode recipient);
+
+ /**
+ * Frees the service address from the given routing node. This allows the network layer to track and close
+ * connections as required.
+ *
+ * @param recipient The node whose service address to free.
+ */
+ public void freeServiceAddress(RoutingNode recipient);
+
+ /**
+ * Send a message to the given recipients. A {@link RoutingNode} contains all the necessary context for sending.
+ *
+ * @param msg The message to send.
+ * @param recipients A list of routing leaf nodes resolved for the message.
+ */
+ public void send(Message msg, List<RoutingNode> recipients);
+
+ /**
+ * Synchronize with internal threads. This method will handshake with all internal threads. This has the implicit
+ * effect of waiting for all active callbacks. Note that this method should never be invoked from a callback since
+ * that would make the thread wait for itself... forever. This method is typically used to untangle during session
+ * shutdown.
+ */
+ public void sync();
+
+ /**
+ * Shuts down the network. This is a blocking call that waits for all scheduled tasks to complete.
+ */
+ public void shutdown();
+
+ /**
+ * Returns a string that represents the connection specs of this network. It is in not a complete address since it
+ * know nothing of the sessions that run on it.
+ *
+ * @return The connection string.
+ */
+ public String getConnectionSpec();
+
+ /**
+ * Returns a reference to a name server mirror.
+ *
+ * @return The mirror object.
+ */
+ public IMirror getMirror();
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkOwner.java b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkOwner.java
new file mode 100644
index 00000000000..42197e086b7
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkOwner.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network;
+
+import com.yahoo.messagebus.Message;
+import com.yahoo.messagebus.Protocol;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.messagebus.ReplyHandler;
+import com.yahoo.text.Utf8Array;
+import com.yahoo.text.Utf8String;
+
+/**
+ * A network owner is the object that instantiates and uses a network. The API to send messages
+ * across the network is part of the Network interface, whereas this interface exposes the required
+ * functionality of a network owner to be able to decode and deliver incoming messages.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public interface NetworkOwner {
+
+ /**
+ * All messages are sent across the network with its accompanying protocol name so that it can be decoded at the
+ * receiving end. The network queries its owner through this function to resolve the protocol from its name.
+ *
+ * @param name The name of the protocol to return.
+ * @return The named protocol.
+ */
+ public Protocol getProtocol(Utf8Array name);
+
+ /**
+ * All messages that arrive in the network layer is passed to its owner through this function.
+ *
+ * @param message The message that just arrived from the network.
+ * @param session The name of the session that is the recipient of the request.
+ */
+ public void deliverMessage(Message message, String session);
+
+ /**
+ * All replies that arrive in the network layer is passed through this to unentangle it from the network thread.
+ *
+ * @param reply The reply that just arrived from the network.
+ * @param handler The handler that is to receive the reply.
+ */
+ public void deliverReply(Reply reply, ReplyHandler handler);
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/ServiceAddress.java b/messagebus/src/main/java/com/yahoo/messagebus/network/ServiceAddress.java
new file mode 100644
index 00000000000..94983f3adb5
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/ServiceAddress.java
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network;
+
+/**
+ * This interface represents an abstract network service; i.e. somewhere to send messages. An instance of this is
+ * retrieved by calling {@link Network#allocServiceAddress(com.yahoo.messagebus.routing.RoutingNode)}.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface ServiceAddress {
+ // empty
+}
+
+
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalNetwork.java b/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalNetwork.java
new file mode 100644
index 00000000000..ffcb853a0a7
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalNetwork.java
@@ -0,0 +1,198 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.local;
+
+import com.yahoo.component.Vtag;
+import com.yahoo.jrt.slobrok.api.IMirror;
+import com.yahoo.messagebus.EmptyReply;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.Message;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.messagebus.ReplyHandler;
+import com.yahoo.messagebus.Routable;
+import com.yahoo.messagebus.TraceNode;
+import com.yahoo.messagebus.network.Network;
+import com.yahoo.messagebus.network.NetworkOwner;
+import com.yahoo.messagebus.network.ServiceAddress;
+import com.yahoo.messagebus.routing.RoutingNode;
+import com.yahoo.text.Utf8String;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import static com.yahoo.messagebus.ErrorCode.NO_ADDRESS_FOR_SERVICE;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class LocalNetwork implements Network {
+
+ private final Executor executor = Executors.newSingleThreadExecutor();
+ private final LocalWire wire;
+ private final String hostId;
+ private volatile NetworkOwner owner;
+
+ public LocalNetwork(final LocalWire wire) {
+ this.wire = wire;
+ this.hostId = wire.newHostId();
+ }
+
+ @Override
+ public boolean waitUntilReady(final double seconds) {
+ return true;
+ }
+
+ @Override
+ public void attach(final NetworkOwner owner) {
+ this.owner = owner;
+ }
+
+ @Override
+ public void registerSession(final String session) {
+ wire.registerService(hostId + "/" + session, this);
+ }
+
+ @Override
+ public void unregisterSession(final String session) {
+ wire.unregisterService(hostId + "/" + session);
+ }
+
+ @Override
+ public boolean allocServiceAddress(final RoutingNode recipient) {
+ final String service = recipient.getRoute().getHop(0).getServiceName();
+ final ServiceAddress address = wire.resolveServiceAddress(service);
+ if (address == null) {
+ recipient.setError(new Error(NO_ADDRESS_FOR_SERVICE, "No address for service '" + service + "'."));
+ return false;
+ }
+ recipient.setServiceAddress(address);
+ return true;
+ }
+
+ @Override
+ public void freeServiceAddress(final RoutingNode recipient) {
+ recipient.setServiceAddress(null);
+ }
+
+ @Override
+ public void send(final Message msg, final List<RoutingNode> recipients) {
+ for (final RoutingNode recipient : recipients) {
+ new MessageEnvelope(this, msg, recipient).send();
+ }
+ }
+
+ private void receiveLater(final MessageEnvelope envelope) {
+ final byte[] payload = envelope.sender.encode(envelope.msg.getProtocol(), envelope.msg);
+ executor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ final Message msg = decode(envelope.msg.getProtocol(), payload, Message.class);
+ msg.getTrace().setLevel(envelope.msg.getTrace().getLevel());
+ msg.setRoute(envelope.msg.getRoute()).getRoute().removeHop(0);
+ msg.setRetryEnabled(envelope.msg.getRetryEnabled());
+ msg.setRetry(envelope.msg.getRetry());
+ msg.setTimeRemaining(envelope.msg.getTimeRemainingNow());
+ msg.pushHandler(new ReplyHandler() {
+
+ @Override
+ public void handleReply(final Reply reply) {
+ new ReplyEnvelope(LocalNetwork.this, envelope, reply).send();
+ }
+ });
+ owner.deliverMessage(msg, LocalServiceAddress.class.cast(envelope.recipient.getServiceAddress())
+ .getSessionName());
+ }
+ });
+ }
+
+ private void receiveLater(final ReplyEnvelope envelope) {
+ final byte[] payload = envelope.sender.encode(envelope.reply.getProtocol(), envelope.reply);
+ executor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ final Reply reply = decode(envelope.reply.getProtocol(), payload, Reply.class);
+ reply.setRetryDelay(envelope.reply.getRetryDelay());
+ reply.getTrace().getRoot().addChild(TraceNode.decode(envelope.reply.getTrace().getRoot().encode()));
+ for (int i = 0, len = envelope.reply.getNumErrors(); i < len; ++i) {
+ final Error error = envelope.reply.getError(i);
+ reply.addError(new Error(error.getCode(),
+ error.getMessage(),
+ error.getService() != null ? error.getService() : envelope.sender.hostId));
+ }
+ owner.deliverReply(reply, envelope.parent.recipient);
+ }
+ });
+ }
+
+ private byte[] encode(final Utf8String protocolName, final Routable toEncode) {
+ if (toEncode.getType() == 0) {
+ return new byte[0];
+ }
+ return owner.getProtocol(protocolName).encode(Vtag.currentVersion, toEncode);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends Routable> T decode(final Utf8String protocolName, final byte[] toDecode, final Class<T> clazz) {
+ if (toDecode.length == 0) {
+ return clazz.cast(new EmptyReply());
+ }
+ return clazz.cast(owner.getProtocol(protocolName).decode(Vtag.currentVersion, toDecode));
+ }
+
+ @Override
+ public void sync() {
+
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+
+ @Override
+ public String getConnectionSpec() {
+ return hostId;
+ }
+
+ @Override
+ public IMirror getMirror() {
+ return wire;
+ }
+
+ private static class MessageEnvelope {
+
+ final LocalNetwork sender;
+ final Message msg;
+ final RoutingNode recipient;
+
+ MessageEnvelope(final LocalNetwork sender, final Message msg, final RoutingNode recipient) {
+ this.sender = sender;
+ this.msg = msg;
+ this.recipient = recipient;
+ }
+
+ void send() {
+ LocalServiceAddress.class.cast(recipient.getServiceAddress())
+ .getNetwork().receiveLater(this);
+ }
+ }
+
+ private static class ReplyEnvelope {
+
+ final LocalNetwork sender;
+ final MessageEnvelope parent;
+ final Reply reply;
+
+ ReplyEnvelope(final LocalNetwork sender, final MessageEnvelope parent, final Reply reply) {
+ this.sender = sender;
+ this.parent = parent;
+ this.reply = reply;
+ }
+
+ void send() {
+ parent.sender.receiveLater(this);
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalServiceAddress.java b/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalServiceAddress.java
new file mode 100644
index 00000000000..9cc96d72e50
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalServiceAddress.java
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.local;
+
+import com.yahoo.messagebus.network.ServiceAddress;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class LocalServiceAddress implements ServiceAddress {
+
+ private final LocalNetwork network;
+ private final String sessionName;
+
+ public LocalServiceAddress(final String serviceName, final LocalNetwork network) {
+ this.network = network;
+ this.sessionName = serviceName.substring(serviceName.lastIndexOf('/') + 1);
+ }
+
+ public LocalNetwork getNetwork() {
+ return network;
+ }
+
+ public String getSessionName() {
+ return sessionName;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalWire.java b/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalWire.java
new file mode 100644
index 00000000000..84ca8c64bc0
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/local/LocalWire.java
@@ -0,0 +1,59 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.local;
+
+import com.yahoo.jrt.slobrok.api.IMirror;
+import com.yahoo.jrt.slobrok.api.Mirror;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class LocalWire implements IMirror {
+
+ private final AtomicInteger serviceId = new AtomicInteger();
+ private final AtomicInteger updateCnt = new AtomicInteger();
+ private final ConcurrentHashMap<String, LocalNetwork> services = new ConcurrentHashMap<>();
+
+ public void registerService(final String serviceName, final LocalNetwork owner) {
+ if (services.putIfAbsent(serviceName, owner) != null) {
+ throw new IllegalStateException();
+ }
+ updateCnt.incrementAndGet();
+ }
+
+ public void unregisterService(final String serviceName) {
+ services.remove(serviceName);
+ updateCnt.incrementAndGet();
+ }
+
+ public LocalServiceAddress resolveServiceAddress(final String serviceName) {
+ final LocalNetwork owner = services.get(serviceName);
+ return owner != null ? new LocalServiceAddress(serviceName, owner) : null;
+ }
+
+ public String newHostId() {
+ return "tcp/local:" + serviceId.getAndIncrement();
+ }
+
+ @Override
+ public Mirror.Entry[] lookup(final String pattern) {
+ final List<Mirror.Entry> out = new ArrayList<>();
+ final Pattern regex = Pattern.compile(pattern.replace("*", "[a-zA-Z0-9_-]+"));
+ for (final String key : services.keySet()) {
+ if (regex.matcher(key).matches()) {
+ out.add(new Mirror.Entry(key, key));
+ }
+ }
+ return out.toArray(new Mirror.Entry[out.size()]);
+ }
+
+ @Override
+ public int updates() {
+ return updateCnt.get();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/package-info.java b/messagebus/src/main/java/com/yahoo/messagebus/network/package-info.java
new file mode 100644
index 00000000000..2fc632f82ba
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * This package declares the API of the network layer required by the message bus.
+ */
+@ExportPackage
+package com.yahoo.messagebus.network;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSClient.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSClient.java
new file mode 100755
index 00000000000..8b16fd44cee
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSClient.java
@@ -0,0 +1,171 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class keeps track of OOS information obtained from a single server. This class is used by the OOSManager class.
+ * Note that since this class is only used inside the transport thread it has no synchronization. Using it directly will
+ * lead to race conditions and possible crashes.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OOSClient implements Runnable, RequestWaiter {
+
+ private Supervisor orb;
+ private Target target = null;
+ private Request request = null;
+ private boolean requestDone = false;
+ private Spec spec;
+ private Task task;
+ private List<String> oosList = new ArrayList<String>();
+ private int requestGen = 0;
+ private int listGen = 0;
+ private int dumpGen = 0;
+ private boolean shutdown = false;
+
+ /**
+ * Create a new OOSClient polling oos information from the given server.
+ *
+ * @param orb The object used for RPC operations.
+ * @param spec The fnet connect spec for oos server.
+ */
+ public OOSClient(Supervisor orb, Spec spec) {
+ this.orb = orb;
+ this.spec = spec;
+
+ task = this.orb.transport().createTask(this);
+ task.scheduleNow();
+ }
+
+ /**
+ * Handle a server reply.
+ */
+ private void handleReply() {
+ if (!request.checkReturnTypes("Si")) {
+ if (target != null) {
+ target.close();
+ target = null;
+ }
+ task.schedule(1.0);
+ return;
+ }
+
+ Values ret = request.returnValues();
+ int retGen = ret.get(1).asInt32();
+ if (requestGen != retGen) {
+ List<String> oos = new ArrayList<String>();
+ oos.addAll(Arrays.asList(ret.get(0).asStringArray()));
+ oosList = oos;
+ requestGen = retGen;
+ listGen = retGen;
+ }
+ task.schedule(0.1);
+ }
+
+ /**
+ * Handle server (re)connect.
+ */
+ private void handleConnect() {
+ if (target == null) {
+ target = orb.connect(spec);
+ requestGen = 0;
+ }
+ }
+
+ /**
+ * Handle server invocation.
+ */
+ private void handleInvoke() {
+ if (target == null) {
+ throw new IllegalStateException("Attempting to invoke a request on a null target.");
+ }
+ request = new Request("fleet.getOOSList");
+ request.parameters().add(new Int32Value(requestGen));
+ request.parameters().add(new Int32Value(60000));
+ target.invokeAsync(request, 70.0, this);
+ }
+
+ /**
+ * Implements runnable. Performs overall server poll logic.
+ */
+ public void run() {
+ if (shutdown) {
+ task.kill();
+ if (target != null) {
+ target.close();
+ }
+ } else if (requestDone) {
+ requestDone = false;
+ handleReply();
+ } else {
+ handleConnect();
+ handleInvoke();
+ }
+ }
+
+ /**
+ * Shut down this OOS client. Invoking this method will take down any active connections and block further activity
+ * from this object.
+ */
+ public void shutdown() {
+ shutdown = true;
+ task.scheduleNow();
+ }
+
+ /**
+ * From FRT_IRequestWait, picks up server replies.
+ *
+ * @param request The request that has completed.
+ */
+ public void handleRequestDone(Request request) {
+ if (request != this.request || requestDone) {
+ throw new IllegalStateException("Multiple invocations of RequestDone().");
+ }
+ requestDone = true;
+ task.scheduleNow();
+ }
+
+ /**
+ * Obtain the connect spec of the OOS server this client is talking to.
+ *
+ * @return OOS server connect spec
+ */
+ public Spec getSpec() {
+ return spec;
+ }
+
+ /**
+ * Check if this client has changed. A client has changed if it has obtain now information after the dumpState
+ * method was last invoked.
+ *
+ * @return True is this client has changed.
+ */
+ public boolean isChanged() {
+ return listGen != dumpGen;
+ }
+
+ /**
+ * Returns whether or not this client has receieved any reply at all from the server it is connected to.
+ *
+ * @return True if initial request has returned.
+ */
+ public boolean isReady() {
+ return listGen != 0;
+ }
+
+ /**
+ * Dump the current oos information known by this client into the given string set.
+ *
+ * @param dst The object used to aggregate oos information.
+ */
+ public void dumpState(Set<String> dst) {
+ dst.addAll(oosList);
+ dumpGen = listGen;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSManager.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSManager.java
new file mode 100755
index 00000000000..c3c973d4e6d
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/OOSManager.java
@@ -0,0 +1,169 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Task;
+import com.yahoo.jrt.slobrok.api.Mirror;
+
+import java.util.*;
+
+/**
+ * This class keeps track of OOS information. A set of servers having OOS information are identified by looking up a
+ * service pattern in the slobrok. These servers are then polled for information. The information is compiled into a
+ * local repository for fast lookup.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OOSManager implements Runnable {
+
+ // An internal flag that indicates whether or not this manager is disabled. This is used to short-circuit any
+ // requests made when the service pattern is null.
+ private boolean disabled;
+
+ // Whether or not this manager has received status information from all connected clients.
+ private boolean ready;
+
+ // The JRT supervisor object.
+ private final Supervisor orb;
+
+ // The JRT slobrok mirror object.
+ private final Mirror mirror;
+
+ // A transport task object used for scheduling this.
+ private Task task;
+
+ // The service pattern used to resolve what services registered in slobrok resolve to OOS servers.
+ private final String servicePattern;
+
+ // A map of OOS clients that each poll a single OOS server. This map will contain an entry for each service that
+ // the service pattern resolves to.
+ private Map<String, OOSClient> clients = Collections.emptyMap();
+
+ // A set of out-of-service service names.
+ private volatile Set<String> oosSet;
+
+ // The generation of the current slobrok resolve.
+ private int slobrokGen = 0;
+
+ // A local copy of the services that the service pattern resolved to after the previous slobrok lookup. This is used
+ // to avoid updating the internal list every time slobrok's generation differs, but instead only when the service
+ // pattern resolves to something different.
+ private List<Mirror.Entry> services;
+
+ /**
+ * Create a new OOSManager. The given service pattern will be looked up in the given slobrok mirror. The resulting
+ * set of services will be polled for oos information.
+ *
+ * @param orb The object used for RPC operations.
+ * @param mirror The slobrok mirror.
+ * @param servicePattern The service pattern for oos servers.
+ */
+ public OOSManager(Supervisor orb, Mirror mirror, String servicePattern) {
+ this.orb = orb;
+ this.mirror = mirror;
+ this.servicePattern = servicePattern;
+
+ disabled = (servicePattern == null || servicePattern.isEmpty());
+ ready = disabled;
+
+ if (!disabled) {
+ task = orb.transport().createTask(this);
+ task.scheduleNow();
+ }
+ }
+
+ /**
+ * Method invoked when this object is run as a task. This method will update the oos information held by this
+ * object.
+ */
+ public void run() {
+ boolean changed = updateFromSlobrok();
+ boolean allOk = mirror.ready();
+ for (OOSClient client : clients.values()) {
+ if (client.isChanged()) {
+ changed = true;
+ }
+ if (!client.isReady()) {
+ allOk = false;
+ }
+ }
+ if (changed) {
+ Set<String> oos = new LinkedHashSet<String>();
+ for (OOSClient client : clients.values()) {
+ client.dumpState(oos);
+ }
+ oosSet = oos;
+ }
+ if (allOk && !ready) {
+ ready = true;
+ }
+ task.schedule(ready ? 1.0 : 0.1);
+ }
+
+ /**
+ * This method will check the local slobrok mirror to make sure that its clients are connected to the appropriate
+ * services. If anything changes this method returns true.
+ *
+ * @return True if anything changed.
+ */
+ private boolean updateFromSlobrok() {
+ if (slobrokGen == mirror.updates()) {
+ return false;
+ }
+ slobrokGen = mirror.updates();
+ List<Mirror.Entry> newServices = Arrays.asList(mirror.lookup(servicePattern));
+ Collections.sort(newServices, new Comparator<Mirror.Entry>() {
+ public int compare(Mirror.Entry lhs, Mirror.Entry rhs) {
+ return lhs.compareTo(rhs);
+ }
+ });
+ if (newServices.equals(services)) {
+ return false;
+ }
+ Map<String, OOSClient> newClients = new HashMap<String, OOSClient>();
+ for (Mirror.Entry service : newServices) {
+ OOSClient client = clients.remove(service.getSpec());
+ if (client == null) {
+ client = new OOSClient(orb, new Spec(service.getSpec()));
+ }
+ newClients.put(service.getSpec(), client);
+ }
+ for (OOSClient client : clients.values()) {
+ client.shutdown();
+ }
+ services = newServices;
+ clients = newClients;
+ return true;
+ }
+
+ /**
+ * Returns whether or not some initial state has been returned.
+ *
+ * @return True, if initial state has been found.
+ */
+ public boolean isReady() {
+ return ready;
+ }
+
+ /**
+ * Returns whether or not the given service has been marked as out of service.
+ *
+ * @param service The service to check.
+ * @return True if the service is out of service.
+ */
+ @SuppressWarnings({ "RedundantIfStatement" })
+ public boolean isOOS(String service) {
+ if (disabled) {
+ return false;
+ }
+ Set<String> s = oosSet;
+ if (s == null) {
+ return false;
+ }
+ if (!s.contains(service)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java
new file mode 100644
index 00000000000..9ab24d662bd
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java
@@ -0,0 +1,530 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.component.Version;
+import com.yahoo.component.VersionSpecification;
+import com.yahoo.component.Vtag;
+import com.yahoo.concurrent.ThreadFactoryFactory;
+import com.yahoo.jrt.*;
+import com.yahoo.jrt.slobrok.api.IMirror;
+import com.yahoo.jrt.slobrok.api.Mirror;
+import com.yahoo.jrt.slobrok.api.Register;
+import com.yahoo.log.LogLevel;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.ErrorCode;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.Network;
+import com.yahoo.messagebus.network.NetworkOwner;
+import com.yahoo.messagebus.routing.Hop;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingNode;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+/**
+ * An RPC implementation of the Network interface.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class RPCNetwork implements Network, MethodHandler {
+
+ private static final Logger log = Logger.getLogger(RPCNetwork.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final Identity identity;
+ private final OOSManager oosManager;
+ private final Supervisor orb;
+ private final RPCTargetPool targetPool;
+ private final RPCServicePool servicePool;
+ private final Acceptor listener;
+ private final Mirror mirror;
+ private final Register register;
+ private final Map<VersionSpecification, RPCSendAdapter> sendAdapters = new HashMap<>();
+ private NetworkOwner owner;
+ private final SlobrokConfigSubscriber slobroksConfig;
+ private final LinkedHashMap<String, Route> lruRouteMap = new LinkedHashMap<>(10000, 0.5f, true);
+ private final ExecutorService sendService =
+ new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(),
+ 0L, TimeUnit.SECONDS,
+ new SynchronousQueue<Runnable>(false),
+ ThreadFactoryFactory.getDaemonThreadFactory("mbus.net"), new ThreadPoolExecutor.CallerRunsPolicy());
+
+ /**
+ * Create an RPCNetwork. The servicePrefix is combined with session names to create service names. If the service
+ * prefix is 'a/b' and the session name is 'c', the resulting service name that identifies the session on the
+ * message bus will be 'a/b/c'
+ *
+ * @param params A complete set of parameters.
+ * @param slobrokConfig subscriber for slobroks config
+ */
+ public RPCNetwork(RPCNetworkParams params, SlobrokConfigSubscriber slobrokConfig) {
+ this.slobroksConfig = slobrokConfig;
+ identity = params.getIdentity();
+ orb = new Supervisor(new Transport());
+ orb.setMaxInputBufferSize(params.getMaxInputBufferSize());
+ orb.setMaxOutputBufferSize(params.getMaxOutputBufferSize());
+ targetPool = new RPCTargetPool(params.getConnectionExpireSecs());
+ servicePool = new RPCServicePool(this, 4096);
+
+ Method method = new Method("mbus.getVersion", "", "s", this);
+ method.methodDesc("Retrieves the message bus version.");
+ method.returnDesc(0, "version", "The message bus version.");
+ orb.addMethod(method);
+
+ try {
+ listener = orb.listen(new Spec(params.getListenPort()));
+ } catch (ListenFailedException e) {
+ orb.transport().shutdown().join();
+ throw new RuntimeException(e);
+ }
+ TargetPoolTask task = new TargetPoolTask(targetPool, orb);
+ task.jrtTask.scheduleNow();
+ register = new Register(orb, slobrokConfig.getSlobroks(), identity.getHostname(), listener.port());
+ mirror = new Mirror(orb, slobrokConfig.getSlobroks());
+ oosManager = new OOSManager(orb, mirror, params.getOOSServerPattern());
+ }
+
+ /**
+ * Create an RPCNetwork. The servicePrefix is combined with session names to create service names. If the service
+ * prefix is 'a/b' and the session name is 'c', the resulting service name that identifies the session on the
+ * message bus will be 'a/b/c'
+ *
+ * @param params A complete set of parameters.
+ */
+ public RPCNetwork(RPCNetworkParams params) {
+ this(params, params.getSlobroksConfig() != null ? new SlobrokConfigSubscriber(params.getSlobroksConfig())
+ : new SlobrokConfigSubscriber(params.getSlobrokConfigId()));
+ }
+
+ /**
+ * The network uses a cache of RPC targets (see {@link RPCTargetPool}) that allows it to save time by reusing open
+ * connections. It works by keeping a set of the most recently used targets open. Calling this method forces all
+ * unused connections to close immediately.
+ */
+ protected void flushTargetPool() {
+ targetPool.flushTargets(true);
+ }
+
+ final Route getRoute(String routeString) {
+ Route route = lruRouteMap.get(routeString);
+ if (route == null) {
+ route = Route.parse(routeString);
+ lruRouteMap.put(routeString, route);
+ }
+ return new Route(route);
+ }
+
+ @Override
+ public boolean waitUntilReady(double seconds) {
+ for (int i = 0; i < seconds * 100; ++i) {
+ if (mirror.ready() && oosManager.isReady()) {
+ return true;
+ }
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ // empty
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean allocServiceAddress(RoutingNode recipient) {
+ Hop hop = recipient.getRoute().getHop(0);
+ String service = hop.getServiceName();
+ Error error = resolveServiceAddress(recipient, service);
+ if (error == null) {
+ return true; // service address resolved
+ }
+ recipient.setError(error);
+ return false; // service address not resolved
+ }
+
+ @Override
+ public void freeServiceAddress(RoutingNode recipient) {
+ RPCTarget target = ((RPCServiceAddress)recipient.getServiceAddress()).getTarget();
+ if (target != null) {
+ target.subRef();
+ }
+ recipient.setServiceAddress(null);
+ }
+
+ @Override
+ public void attach(NetworkOwner owner) {
+ if (this.owner != null) {
+ throw new IllegalStateException("Network is already attached to another owner.");
+ }
+ this.owner = owner;
+
+ RPCSendAdapter adapter = new RPCSendV1();
+ addSendAdapter(new VersionSpecification(5), adapter);
+ addSendAdapter(new VersionSpecification(6), adapter);
+ }
+
+ @Override
+ public void registerSession(String session) {
+ register.registerName(identity.getServicePrefix() + "/" + session);
+ }
+
+ @Override
+ public void unregisterSession(String session) {
+ register.unregisterName(identity.getServicePrefix() + "/" + session);
+ }
+
+ @Override
+ public void sync() {
+ SyncTask sh = new SyncTask();
+ orb.transport().perform(sh);
+ sh.await();
+ }
+
+ @Override
+ public void shutdown() {
+ destroy();
+ }
+
+ @Override
+ public String getConnectionSpec() {
+ return "tcp/" + identity.getHostname() + ":" + listener.port();
+ }
+
+ @Override
+ public IMirror getMirror() {
+ return mirror;
+ }
+
+ @Override
+ public void invoke(Request request) {
+ request.returnValues().add(new StringValue(getVersion().toString()));
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "RPCNetwork destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public void send(Message msg, List<RoutingNode> recipients) {
+ SendContext ctx = new SendContext(this, msg, recipients);
+ double timeout = ctx.msg.getTimeRemainingNow() / 1000.0;
+ for (RoutingNode recipient : ctx.recipients) {
+ RPCServiceAddress address = (RPCServiceAddress)recipient.getServiceAddress();
+ address.getTarget().resolveVersion(timeout, ctx);
+ }
+ }
+
+ /**
+ * This method is a callback invoked after {@link #send(Message, List)} once the version of all recipients have been
+ * resolved. If all versions were resolved ahead of time, this method is invoked by the same thread as the former.
+ * If not, this method is invoked by the network thread during the version callback.
+ *
+ * @param ctx All the required send-data.
+ */
+ private void send(SendContext ctx) {
+ if (destroyed.get()) {
+ replyError(ctx, ErrorCode.NETWORK_SHUTDOWN,
+ "Network layer has performed shutdown.");
+ } else if (ctx.hasError) {
+ replyError(ctx, ErrorCode.HANDSHAKE_FAILED,
+ "An error occured while resolving version.");
+ } else {
+ sendService.execute(new SendTask(owner.getProtocol(ctx.msg.getProtocol()), ctx));
+ }
+ }
+
+ /**
+ * Sets the destroyed flag to true. The very first time this method is called, it cleans up all its dependencies.
+ * Even if you retain a reference to this object, all of its content is allowed to be garbage collected.
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (!destroyed.getAndSet(true)) {
+ if (slobroksConfig != null) {
+ slobroksConfig.shutdown();
+ }
+ register.shutdown();
+ mirror.shutdown();
+ listener.shutdown().join();
+ orb.transport().shutdown().join();
+ targetPool.flushTargets(true);
+ sendService.shutdown();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the version of this network. This gets called when the "mbus.getVersion" method is invoked on this
+ * network, and is separated into its own function so that unit tests can override it to simulate other versions
+ * than current.
+ *
+ * @return The version to claim to be.
+ */
+ protected Version getVersion() {
+ return Vtag.currentVersion;
+ }
+
+ /**
+ * Resolves and assigns a service address for the given recipient using the given address. This is called by the
+ * {@link #allocServiceAddress(RoutingNode)} method. The target allocated here is released when the routing node
+ * calls {@link #freeServiceAddress(RoutingNode)}.
+ *
+ * @param recipient The recipient to assign the service address to.
+ * @param serviceName The name of the service to resolve.
+ * @return Any error encountered, or null.
+ */
+ public Error resolveServiceAddress(RoutingNode recipient, String serviceName) {
+ if (oosManager.isOOS(serviceName)) {
+ return new Error(ErrorCode.SERVICE_OOS,
+ "The service '" + serviceName + "' has been marked as out of service.");
+ }
+ RPCServiceAddress ret = servicePool.resolve(serviceName);
+ if (ret == null) {
+ return new Error(ErrorCode.NO_ADDRESS_FOR_SERVICE,
+ "The address of service '" + serviceName + "' could not be resolved. It is not currently " +
+ "registered with the Vespa name server. " +
+ "The service must be having problems, or the routing configuration is wrong.");
+ }
+ RPCTarget target = targetPool.getTarget(orb, ret);
+ if (target == null) {
+ return new Error(ErrorCode.CONNECTION_ERROR,
+ "Failed to connect to service '" + serviceName + "'.");
+ }
+ ret.setTarget(target); // free by freeServiceAddress()
+ recipient.setServiceAddress(ret);
+ return null; // no error
+ }
+
+ /**
+ * Registers a send adapter for a given version. This will overwrite whatever is already registered under the same
+ * version.
+ *
+ * @param version The version for which to register an adapter.
+ * @param adapter The adapter to register.
+ */
+ private void addSendAdapter(VersionSpecification version, RPCSendAdapter adapter) {
+ adapter.attach(this);
+ sendAdapters.put(version, adapter);
+ }
+
+ /**
+ * Determines and returns the send adapter that is compatible with the given version. If no adapter can be found,
+ * this method returns null.
+ *
+ * @param version The version for which to return an adapter.
+ * @return The compatible adapter.
+ */
+ private RPCSendAdapter getSendAdapter(Version version) {
+ for (Map.Entry<VersionSpecification, RPCSendAdapter> entry : sendAdapters.entrySet()) {
+ if (entry.getKey().matches(version)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Deliver an error reply to the recipients of a {@link SendContext} in a way that avoids entanglement.
+ *
+ * @param ctx The send context that contains the recipient data.
+ * @param errCode The error code to return.
+ * @param errMsg The error string to return.
+ */
+ private void replyError(SendContext ctx, int errCode, String errMsg) {
+ for (RoutingNode recipient : ctx.recipients) {
+ Reply reply = new EmptyReply();
+ reply.getTrace().setLevel(ctx.traceLevel);
+ reply.addError(new Error(errCode, errMsg));
+ owner.deliverReply(reply, recipient);
+ }
+ }
+
+ /**
+ * Get the owner of this network
+ *
+ * @return network owner
+ */
+ NetworkOwner getOwner() {
+ return owner;
+ }
+
+ /**
+ * Returns the identity of this network.
+ *
+ * @return The identity.
+ */
+ public Identity getIdentity() {
+ return identity;
+ }
+
+ /**
+ * Obtain the port number this network listens to
+ *
+ * @return listening port number
+ */
+ public int getPort() {
+ return listener.port();
+ }
+
+ /**
+ * Returns the JRT supervisor.
+ *
+ * @return The supervisor.
+ */
+ Supervisor getSupervisor() {
+ return orb;
+ }
+
+ /**
+ * Returns the oos manager object so that it can be manually queried about out-of-service services.
+ *
+ * @return The oos manager.
+ */
+ public OOSManager getOOSManager() {
+ return oosManager;
+ }
+
+ private class SendTask implements Runnable {
+
+ final Protocol protocol;
+ final SendContext ctx;
+
+ SendTask(Protocol protocol, SendContext ctx) {
+ this.protocol = protocol;
+ this.ctx = ctx;
+ }
+
+ public void run() {
+ long timeRemaining = ctx.msg.getTimeRemainingNow();
+ if (timeRemaining <= 0) {
+ replyError(ctx, ErrorCode.TIMEOUT, "Aborting transmission because zero time remains.");
+ return;
+ }
+ byte[] payload;
+ try {
+ payload = protocol.encode(ctx.version, ctx.msg);
+ } catch (Exception e) {
+ StringWriter out = new StringWriter();
+ e.printStackTrace(new PrintWriter(out));
+ replyError(ctx, ErrorCode.ENCODE_ERROR, out.toString());
+ return;
+ }
+ if (payload == null || payload.length == 0) {
+ replyError(ctx, ErrorCode.ENCODE_ERROR,
+ "Protocol '" + ctx.msg.getProtocol() + "' failed to encode message.");
+ return;
+ }
+ RPCSendAdapter adapter = getSendAdapter(ctx.version);
+ if (adapter == null) {
+ replyError(ctx, ErrorCode.INCOMPATIBLE_VERSION,
+ "Can not send to version '" + ctx.version + "' recipient.");
+ return;
+ }
+ for (RoutingNode recipient : ctx.recipients) {
+ adapter.send(recipient, ctx.version, payload, timeRemaining);
+ }
+ }
+ }
+
+ /**
+ * Implements a helper class for {@link RPCNetwork#sync()}. It provides a blocking method {@link #await()} that will
+ * wait until the internal state of this object is set to 'done'. By scheduling this task in the network thread and
+ * then calling this method, we achieve handshaking with the network thread.
+ */
+ private static class SyncTask implements Runnable {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void run() {
+ latch.countDown();
+ }
+
+ public void await() {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Implements a helper class for {@link RPCNetwork#send(com.yahoo.messagebus.Message, java.util.List)}. It works by
+ * encapsulating all the data required for sending a message, but postponing the call to {@link
+ * RPCNetwork#send(com.yahoo.messagebus.network.rpc.RPCNetwork.SendContext)} until the version of all targets have
+ * been resolved.
+ */
+ private static class SendContext implements RPCTarget.VersionHandler {
+
+ final RPCNetwork net;
+ final Message msg;
+ final int traceLevel;
+ final List<RoutingNode> recipients = new LinkedList<>();
+ boolean hasError = false;
+ int pending;
+ Version version;
+
+ SendContext(RPCNetwork net, Message msg, List<RoutingNode> recipients) {
+ this.net = net;
+ this.msg = msg;
+ this.traceLevel = this.msg.getTrace().getLevel();
+ this.recipients.addAll(recipients);
+ this.pending = this.recipients.size();
+ this.version = this.net.getVersion();
+ }
+
+ @Override
+ public void handleVersion(Version version) {
+ boolean shouldSend = false;
+ synchronized (this) {
+ if (version == null) {
+ hasError = true;
+ } else if (version.compareTo(this.version) < 0) {
+ this.version = version;
+ }
+ if (--pending == 0) {
+ shouldSend = true;
+ }
+ }
+ if (shouldSend) {
+ net.send(this);
+ }
+ }
+ }
+
+ /**
+ * Implements a helper class to invoke {@link RPCTargetPool#flushTargets(boolean)} once every second. This is to
+ * unentangle the target pool from the scheduler.
+ */
+ private static class TargetPoolTask implements Runnable {
+
+ final RPCTargetPool pool;
+ final Task jrtTask;
+
+ TargetPoolTask(RPCTargetPool pool, Supervisor orb) {
+ this.pool = pool;
+ this.jrtTask = orb.transport().createTask(this);
+ this.jrtTask.schedule(1.0);
+ }
+
+ @Override
+ public void run() {
+ pool.flushTargets(false);
+ jrtTask.schedule(1.0);
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java
new file mode 100755
index 00000000000..5f66414ef45
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java
@@ -0,0 +1,210 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.cloud.config.SlobroksConfig;
+
+/**
+ * To facilitate several configuration parameters to the {@link RPCNetwork} constructor, all parameters are held by this
+ * class. This class has reasonable default values for each parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RPCNetworkParams {
+
+ private Identity identity = new Identity("");
+ private String slobrokConfigId = "admin/slobrok.0";
+ private SlobroksConfig slobroksConfig = null;
+ private String oosServerPattern = "";
+ private int listenPort = 0;
+ private int maxInputBufferSize = 256 * 1024;
+ private int maxOutputBufferSize = 256 * 1024;
+ private double connectionExpireSecs = 30;
+
+ /**
+ * Constructs a new instance of this class with reasonable default values.
+ */
+ public RPCNetworkParams() {
+ // empty
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param params The object to copy.
+ */
+ public RPCNetworkParams(RPCNetworkParams params) {
+ identity = new Identity(params.identity);
+ slobrokConfigId = params.slobrokConfigId;
+ slobroksConfig = params.slobroksConfig;
+ oosServerPattern = params.oosServerPattern;
+ listenPort = params.listenPort;
+ connectionExpireSecs = params.connectionExpireSecs;
+ maxInputBufferSize = params.maxInputBufferSize;
+ maxOutputBufferSize = params.maxOutputBufferSize;
+ }
+
+ /**
+ * Returns the identity to use for the network.
+ *
+ * @return The identity.
+ */
+ public Identity getIdentity() {
+ return identity;
+ }
+
+ /**
+ * Sets the identity to use for the network.
+ *
+ * @param identity The new identity.
+ * @return This, to allow chaining.
+ */
+ public RPCNetworkParams setIdentity(Identity identity) {
+ this.identity = identity;
+ return this;
+ }
+
+ /**
+ * Returns the config id of the slobrok config.
+ *
+ * @return The config id.
+ */
+ public String getSlobrokConfigId() {
+ return slobrokConfigId;
+ }
+
+ /**
+ * Sets the config id of the slobrok config. Setting this to null string will revert to the default slobrok config
+ * identifier.
+ *
+ * @param slobrokConfigId The new config id.
+ * @return This, to allow chaining.
+ */
+ public RPCNetworkParams setSlobrokConfigId(String slobrokConfigId) {
+ this.slobrokConfigId = slobrokConfigId;
+ return this;
+ }
+
+ /**
+ * Returns the 'slobroks' config, if set, otherwise null.
+ * @return The 'slobroks' config, if set, otherwise null.
+ */
+ public SlobroksConfig getSlobroksConfig() {
+ return slobroksConfig;
+ }
+
+ /**
+ * Sets the 'slobroks' config object. Setting this to null will revert to self-subscribing using {@link #getSlobrokConfigId}.
+ *
+ * @param slobroksConfig the new slobroks config to use, or null.
+ * @return This, to allow chaining.
+ */
+ public RPCNetworkParams setSlobroksConfig(SlobroksConfig slobroksConfig) {
+ this.slobroksConfig = slobroksConfig;
+ return this;
+ }
+
+ /**
+ * Returns the config id pattern used to lookup OOS servers.
+ *
+ * @return The config id.
+ */
+ public String getOOSServerPattern() {
+ return oosServerPattern;
+ }
+
+ /**
+ * Sets the config id pattern used to lookup OOS servers.
+ *
+ * @param oosServerPattern The server pattern.
+ * @return This, to allow chaining.
+ */
+ public RPCNetworkParams setOOSServerPattern(String oosServerPattern) {
+ this.oosServerPattern = oosServerPattern;
+ return this;
+ }
+
+ /**
+ * Returns the port to listen to.
+ *
+ * @return The port.
+ */
+ public int getListenPort() {
+ return listenPort;
+ }
+
+ /**
+ * Sets the port to listen to.
+ *
+ * @param listenPort The new port.
+ * @return This, to allow chaining.
+ */
+ public RPCNetworkParams setListenPort(int listenPort) {
+ this.listenPort = listenPort;
+ return this;
+ }
+
+ /**
+ * Returns the number of seconds before an idle network connection expires.
+ *
+ * @return The number of seconds.
+ */
+ public double getConnectionExpireSecs() {
+ return connectionExpireSecs;
+ }
+
+ /**
+ * Sets the number of seconds before an idle network connection expires.
+ *
+ * @param secs The number of seconds.
+ * @return This, to allow chaining.
+ */
+ public RPCNetworkParams setConnectionExpireSecs(double secs) {
+ this.connectionExpireSecs = secs;
+ return this;
+ }
+
+ /**
+ * Returns the maximum input buffer size allowed for the underlying FNET connection.
+ *
+ * @return The maximum number of bytes.
+ */
+ public int getMaxInputBufferSize() {
+ return maxInputBufferSize;
+ }
+
+ /**
+ * Sets the maximum input buffer size allowed for the underlying FNET connection. Using the value 0 means that there
+ * is no limit; the connection will not free any allocated memory until it is cleaned up. This might potentially
+ * save alot of allocation time.
+ *
+ * @param maxInputBufferSize The maximum number of bytes.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams setMaxInputBufferSize(int maxInputBufferSize) {
+ this.maxInputBufferSize = maxInputBufferSize;
+ return this;
+ }
+
+ /**
+ * Returns the maximum output buffer size allowed for the underlying FNET connection.
+ *
+ * @return The maximum number of bytes.
+ */
+ int getMaxOutputBufferSize() {
+ return maxOutputBufferSize;
+ }
+
+ /**
+ * Sets the maximum output buffer size allowed for the underlying FNET connection. Using the value 0 means that
+ * there is no limit; the connection will not free any allocated memory until it is cleaned up. This might
+ * potentially save alot of allocation time.
+ *
+ * @param maxOutputBufferSize The maximum number of bytes.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams setMaxOutputBufferSize(int maxOutputBufferSize) {
+ this.maxOutputBufferSize = maxOutputBufferSize;
+ return this;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendAdapter.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendAdapter.java
new file mode 100755
index 00000000000..63fa3639cd9
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendAdapter.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.component.Version;
+import com.yahoo.messagebus.routing.RoutingNode;
+
+/**
+ * This interface defines the necessary methods to process incoming and send outgoing RPC requests. The {@link
+ * RPCNetwork} maintains a list of supported RPC signatures, and dispatches requests to the corresponding adapter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface RPCSendAdapter {
+
+ /**
+ * Attaches this adapter to the given network.
+ *
+ * @param net The network to attach to.
+ */
+ public void attach(RPCNetwork net);
+
+ /**
+ * Performs the actual sending to the given recipient.
+ *
+ * @param recipient The recipient to send to.
+ * @param version The version for which the payload is serialized.
+ * @param payload The already serialized payload of the message to send.
+ * @param timeRemaining The time remaining until the message expires.
+ */
+ public void send(RoutingNode recipient, Version version, byte[] payload, long timeRemaining);
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendV1.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendV1.java
new file mode 100755
index 00000000000..b3709869ba6
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCSendV1.java
@@ -0,0 +1,321 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.component.Version;
+import com.yahoo.jrt.*;
+import com.yahoo.jrt.StringValue;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.ErrorCode;
+import com.yahoo.messagebus.ReplyHandler;
+import com.yahoo.messagebus.routing.Hop;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingNode;
+import com.yahoo.text.Utf8Array;
+import com.yahoo.text.Utf8String;
+
+/**
+ * Implements the request adapter for method "mbus.send1".
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RPCSendV1 implements MethodHandler, ReplyHandler, RequestWaiter, RPCSendAdapter {
+
+ private final String METHOD_NAME = "mbus.send1";
+ private final String METHOD_PARAMS = "sssbilsxi";
+ private final String METHOD_RETURN = "sdISSsxs";
+ private RPCNetwork net = null;
+ private String clientIdent = "client";
+ private String serverIdent = "server";
+
+ @Override
+ public void attach(RPCNetwork net) {
+ this.net = net;
+ String prefix = net.getIdentity().getServicePrefix();
+ if (prefix != null && prefix.length() > 0) {
+ clientIdent = "'" + prefix + "'";
+ serverIdent = clientIdent;
+ }
+
+ Method method = new Method(METHOD_NAME, METHOD_PARAMS, METHOD_RETURN, this);
+ method.methodDesc("Send a message bus request and get a reply back.");
+ method.paramDesc(0, "version", "The version of the message.")
+ .paramDesc(1, "route", "Names of additional hops to visit.")
+ .paramDesc(2, "session", "The local session that should receive this message.")
+ .paramDesc(3, "retryEnabled", "Whether or not this message can be resent.")
+ .paramDesc(4, "retry", "The number of times the sending of this message has been retried.")
+ .paramDesc(5, "timeRemaining", "The number of milliseconds until timeout.")
+ .paramDesc(6, "protocol", "The name of the protocol that knows how to decode this message.")
+ .paramDesc(7, "payload", "The protocol specific message payload.")
+ .paramDesc(8, "level", "The trace level of the message.");
+ method.returnDesc(0, "version", "The lowest version the message was serialized as.")
+ .returnDesc(1, "retryDelay", "The retry request of the reply.")
+ .returnDesc(2, "errorCodes", "The reply error codes.")
+ .returnDesc(3, "errorMessages", "The reply error messages.")
+ .returnDesc(4, "errorServices", "The reply error service names.")
+ .returnDesc(5, "protocol", "The name of the protocol that knows how to decode this reply.")
+ .returnDesc(6, "payload", "The protocol specific reply payload.")
+ .returnDesc(7, "trace", "A string representation of the trace.");
+ net.getSupervisor().addMethod(method);
+ }
+
+ @Override
+ public void send(RoutingNode recipient, Version version, byte[] payload, long timeRemaining) {
+ SendContext ctx = new SendContext(recipient, timeRemaining);
+ RPCServiceAddress address = (RPCServiceAddress)recipient.getServiceAddress();
+ Message msg = recipient.getMessage();
+ Route route = new Route(recipient.getRoute());
+ Hop hop = route.removeHop(0);
+
+ Request req = new Request(METHOD_NAME);
+ req.parameters().add(new StringValue(version.toString()));
+ req.parameters().add(new StringValue(route.toString()));
+ req.parameters().add(new StringValue(address.getSessionName()));
+ req.parameters().add(new Int8Value(msg.getRetryEnabled() ? (byte)1 : (byte)0));
+ req.parameters().add(new Int32Value(msg.getRetry()));
+ req.parameters().add(new Int64Value(timeRemaining));
+ req.parameters().add(new StringValue(msg.getProtocol()));
+ req.parameters().add(new DataValue(payload));
+ req.parameters().add(new Int32Value(ctx.trace.getLevel()));
+
+ if (ctx.trace.shouldTrace(TraceLevel.SEND_RECEIVE)) {
+ ctx.trace.trace(TraceLevel.SEND_RECEIVE,
+ "Sending message (version " + version + ") from " + clientIdent + " to '" +
+ address.getServiceName() + "' with " + ctx.timeout + " seconds timeout.");
+ }
+
+ if (hop.getIgnoreResult()) {
+ address.getTarget().getJRTTarget().invokeVoid(req);
+ if (ctx.trace.shouldTrace(TraceLevel.SEND_RECEIVE)) {
+ ctx.trace.trace(TraceLevel.SEND_RECEIVE,
+ "Not waiting for a reply from '" + address.getServiceName() + "'.");
+ }
+ Reply reply = new EmptyReply();
+ reply.getTrace().swap(ctx.trace);
+ net.getOwner().deliverReply(reply, recipient);
+ } else {
+ req.setContext(ctx);
+ address.getTarget().getJRTTarget().invokeAsync(req, ctx.timeout, this);
+ }
+ req.discardParameters(); // allow garbage collection of request parameters
+ }
+
+ @Override
+ public void handleRequestDone(Request req) {
+ SendContext ctx = (SendContext)req.getContext();
+ String serviceName = ((RPCServiceAddress)ctx.recipient.getServiceAddress()).getServiceName();
+ Reply reply = null;
+ Error error = null;
+ if (!req.checkReturnTypes(METHOD_RETURN)) {
+ // Map all known JRT errors to the appropriate message bus error.
+ reply = new EmptyReply();
+ switch (req.errorCode()) {
+ case com.yahoo.jrt.ErrorCode.TIMEOUT:
+ error = new Error(com.yahoo.messagebus.ErrorCode.TIMEOUT,
+ "A timeout occured while waiting for '" + serviceName + "' (" +
+ ctx.timeout + " seconds expired); " + req.errorMessage());
+ break;
+ case com.yahoo.jrt.ErrorCode.CONNECTION:
+ error = new Error(com.yahoo.messagebus.ErrorCode.CONNECTION_ERROR,
+ "A connection error occured for '" + serviceName + "'; " + req.errorMessage());
+ break;
+ default:
+ error = new Error(com.yahoo.messagebus.ErrorCode.NETWORK_ERROR,
+ "A network error occured for '" + serviceName + "'; " + req.errorMessage());
+ }
+ } else {
+ // Retrieve all reply components from JRT request object.
+ Version version = new Version(req.returnValues().get(0).asUtf8Array());
+ double retryDelay = req.returnValues().get(1).asDouble();
+ int[] errorCodes = req.returnValues().get(2).asInt32Array();
+ String[] errorMessages = req.returnValues().get(3).asStringArray();
+ String[] errorServices = req.returnValues().get(4).asStringArray();
+ Utf8Array protocolName = req.returnValues().get(5).asUtf8Array();
+ byte[] payload = req.returnValues().get(6).asData();
+ String replyTrace = req.returnValues().get(7).asString();
+
+ // Make sure that the owner understands the protocol.
+ if (payload.length > 0) {
+ Protocol protocol = net.getOwner().getProtocol(protocolName);
+ if (protocol != null) {
+ Routable routable = protocol.decode(version, payload);
+ if (routable != null) {
+ if (routable instanceof Reply) {
+ reply = (Reply)routable;
+ } else {
+ error = new Error(com.yahoo.messagebus.ErrorCode.DECODE_ERROR,
+ "Payload decoded to a reply when expecting a message.");
+ }
+ } else {
+ error = new Error(com.yahoo.messagebus.ErrorCode.DECODE_ERROR,
+ "Protocol '" + protocol.getName() + "' failed to decode routable.");
+ }
+ } else {
+ error = new Error(com.yahoo.messagebus.ErrorCode.UNKNOWN_PROTOCOL,
+ "Protocol '" + protocolName + "' is not known by " + serverIdent + ".");
+ }
+ }
+ if (reply == null) {
+ reply = new EmptyReply();
+ }
+ reply.setRetryDelay(retryDelay);
+ for (int i = 0; i < errorCodes.length && i < errorMessages.length; i++) {
+ reply.addError(new Error(errorCodes[i],
+ errorMessages[i],
+ errorServices[i].length() > 0 ? errorServices[i] : serviceName));
+ }
+ if (ctx.trace.getLevel() > 0) {
+ ctx.trace.getRoot().addChild(TraceNode.decode(replyTrace));
+ }
+ }
+ if (ctx.trace.shouldTrace(TraceLevel.SEND_RECEIVE)) {
+ ctx.trace.trace(TraceLevel.SEND_RECEIVE,
+ "Reply (type " + reply.getType() + ") received at " + clientIdent + ".");
+ }
+ reply.getTrace().swap(ctx.trace);
+ if (error != null) {
+ reply.addError(error);
+ }
+ net.getOwner().deliverReply(reply, ctx.recipient);
+ }
+
+ @Override
+ public void invoke(Request request) {
+ request.detach();
+ Version version = new Version(request.parameters().get(0).asUtf8Array());
+ String route = request.parameters().get(1).asString();
+ String session = request.parameters().get(2).asString();
+ boolean retryEnabled = (request.parameters().get(3).asInt8() != 0);
+ int retry = request.parameters().get(4).asInt32();
+ long timeRemaining = request.parameters().get(5).asInt64();
+ Utf8Array protocolName = request.parameters().get(6).asUtf8Array();
+ byte[] payload = request.parameters().get(7).asData();
+ int traceLevel = request.parameters().get(8).asInt32();
+
+ request.discardParameters(); // allow garbage collection of request parameters
+
+ // Make sure that the owner understands the protocol.
+ Protocol protocol = net.getOwner().getProtocol(protocolName);
+ if (protocol == null) {
+ replyError(request, version, traceLevel,
+ new com.yahoo.messagebus.Error(ErrorCode.UNKNOWN_PROTOCOL,
+ "Protocol '" + protocolName + "' is not known by " + serverIdent + "."));
+ return;
+ }
+ Routable routable = protocol.decode(version, payload);
+ if (routable == null) {
+ replyError(request, version, traceLevel,
+ new Error(ErrorCode.DECODE_ERROR,
+ "Protocol '" + protocol.getName() + "' failed to decode routable."));
+ return;
+ }
+ if (routable instanceof Reply) {
+ replyError(request, version, traceLevel,
+ new Error(ErrorCode.DECODE_ERROR,
+ "Payload decoded to a reply when expecting a message."));
+ return;
+ }
+ Message msg = (Message)routable;
+ if (route != null && route.length() > 0) {
+ msg.setRoute(net.getRoute(route));
+ }
+ msg.setContext(new ReplyContext(request, version));
+ msg.pushHandler(this);
+ msg.setRetryEnabled(retryEnabled);
+ msg.setRetry(retry);
+ msg.setTimeReceivedNow();
+ msg.setTimeRemaining(timeRemaining);
+ msg.getTrace().setLevel(traceLevel);
+ if (msg.getTrace().shouldTrace(TraceLevel.SEND_RECEIVE)) {
+ msg.getTrace().trace(TraceLevel.SEND_RECEIVE,
+ "Message (type " + msg.getType() + ") received at " + serverIdent + " for session '" + session + "'.");
+ }
+ net.getOwner().deliverMessage(msg, session);
+ }
+
+ @Override
+ public void handleReply(Reply reply) {
+ ReplyContext ctx = (ReplyContext)reply.getContext();
+ reply.setContext(null);
+
+ // Add trace information.
+ if (reply.getTrace().shouldTrace(TraceLevel.SEND_RECEIVE)) {
+ reply.getTrace().trace(TraceLevel.SEND_RECEIVE,
+ "Sending reply (version " + ctx.version + ") from " + serverIdent + ".");
+ }
+
+ // Encode and return the reply through the RPC request.
+ byte[] payload = new byte[0];
+ if (reply.getType() != 0) {
+ Protocol protocol = net.getOwner().getProtocol(reply.getProtocol());
+ if (protocol != null) {
+ payload = protocol.encode(ctx.version, reply);
+ }
+ if (payload == null || payload.length == 0) {
+ reply.addError(new Error(ErrorCode.ENCODE_ERROR,
+ "An error occured while encoding the reply."));
+ }
+ }
+ int[] eCodes = new int[reply.getNumErrors()];
+ String[] eMessages = new String[reply.getNumErrors()];
+ String[] eServices = new String[reply.getNumErrors()];
+ for (int i = 0; i < reply.getNumErrors(); ++i) {
+ Error error = reply.getError(i);
+ eCodes[i] = error.getCode();
+ eMessages[i] = error.getMessage();
+ eServices[i] = error.getService() != null ? error.getService() : "";
+ }
+ ctx.request.returnValues().add(new StringValue(ctx.version.toString()));
+ ctx.request.returnValues().add(new DoubleValue(reply.getRetryDelay()));
+ ctx.request.returnValues().add(new Int32Array(eCodes));
+ ctx.request.returnValues().add(new StringArray(eMessages));
+ ctx.request.returnValues().add(new StringArray(eServices));
+ ctx.request.returnValues().add(new StringValue(reply.getProtocol()));
+ ctx.request.returnValues().add(new DataValue(payload));
+ ctx.request.returnValues().add(new StringValue(
+ reply.getTrace().getRoot() != null ?
+ reply.getTrace().getRoot().encode() :
+ ""));
+ ctx.request.returnRequest();
+ }
+
+ /**
+ * Send an error reply for a given request.
+ *
+ * @param request The JRT request to reply to.
+ * @param version The version to serialize for.
+ * @param traceLevel The trace level to set in the reply.
+ * @param err The error to reply with.
+ */
+ private void replyError(Request request, Version version, int traceLevel, Error err) {
+ Reply reply = new EmptyReply();
+ reply.setContext(new ReplyContext(request, version));
+ reply.getTrace().setLevel(traceLevel);
+ reply.addError(err);
+ handleReply(reply);
+ }
+
+ private static class SendContext {
+
+ final RoutingNode recipient;
+ final Trace trace;
+ final double timeout;
+
+ SendContext(RoutingNode recipient, long timeRemaining) {
+ this.recipient = recipient;
+ trace = new Trace(recipient.getTrace().getLevel());
+ timeout = timeRemaining * 0.001;
+ }
+ }
+
+ private static class ReplyContext {
+
+ final Request request;
+ final Version version;
+
+ public ReplyContext(Request request, Version version) {
+ this.request = request;
+ this.version = version;
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java
new file mode 100644
index 00000000000..09d50452fce
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java
@@ -0,0 +1,71 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.slobrok.api.IMirror;
+import com.yahoo.jrt.slobrok.api.Mirror;
+
+import java.util.Random;
+
+/**
+ * An RPCService represents a set of remote sessions matching a service pattern. The sessions are monitored using the
+ * slobrok. If multiple sessions are available, round robin is used to balance load between them.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class RPCService {
+
+ private final IMirror mirror;
+ private final String pattern;
+ private int addressIdx = new Random().nextInt(Integer.MAX_VALUE);
+ private int addressGen = 0;
+ private Mirror.Entry[] addressList = null;
+
+ /**
+ * Create a new RPCService backed by the given network and using the given service pattern.
+ *
+ * @param mirror The naming server to send queries to.
+ * @param pattern The pattern to use when querying.
+ */
+ public RPCService(IMirror mirror, String pattern) {
+ this.mirror = mirror;
+ this.pattern = pattern;
+ }
+
+ /**
+ * Resolve a concrete address from this service. This service may represent multiple remote sessions, so this will
+ * select one that is online.
+ *
+ * @return A concrete service address.
+ */
+ public RPCServiceAddress resolve() {
+ if (pattern.startsWith("tcp/")) {
+ int pos = pattern.lastIndexOf('/');
+ if (pos > 0 && pos < pattern.length() - 1) {
+ RPCServiceAddress ret = new RPCServiceAddress(pattern, pattern.substring(0, pos));
+ if (!ret.isMalformed()) {
+ return ret;
+ }
+ }
+ } else {
+ if (addressGen != mirror.updates()) {
+ addressGen = mirror.updates();
+ addressList = mirror.lookup(pattern);
+ }
+ if (addressList != null && addressList.length > 0) {
+ addressIdx = ++addressIdx % addressList.length;
+ Mirror.Entry entry = addressList[addressIdx];
+ return new RPCServiceAddress(entry.getName(), entry.getSpec());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the pattern used when querying for the naming server for addresses. This is given at construtor time.
+ *
+ * @return The service pattern.
+ */
+ String getPattern() {
+ return pattern;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java
new file mode 100755
index 00000000000..c95769d6840
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java
@@ -0,0 +1,116 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.messagebus.network.ServiceAddress;
+import com.yahoo.jrt.Spec;
+
+/**
+ * Implements the {@link ServiceAddress} interface for the RPC network.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class RPCServiceAddress implements ServiceAddress {
+
+ private final String serviceName;
+ private final String sessionName;
+ private final Spec connectionSpec;
+ private RPCTarget target;
+
+ /**
+ * Constructs a service address from the given specifications. The last component of the service is stored as the
+ * session name.
+ *
+ * @param serviceName The full service name of the address.
+ * @param connectionSpec The connection specification.
+ */
+ public RPCServiceAddress(String serviceName, String connectionSpec) {
+ this.serviceName = serviceName;
+ int pos = serviceName.lastIndexOf('/');
+ if (pos > 0 && pos < serviceName.length() - 1) {
+ sessionName = serviceName.substring(pos + 1);
+ } else {
+ sessionName = null;
+ }
+ this.connectionSpec = new Spec(connectionSpec);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof RPCServiceAddress)) {
+ return false;
+ }
+ RPCServiceAddress rhs = (RPCServiceAddress)obj;
+ if (!serviceName.equals(rhs.serviceName)) {
+ return false;
+ }
+ if (!connectionSpec.host().equals(rhs.connectionSpec.host())) {
+ return false;
+ }
+ if (connectionSpec.port() != rhs.connectionSpec.port()) {
+ return false;
+ }
+ if (!sessionName.equals(rhs.sessionName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return serviceName.hashCode() + connectionSpec.hashCode() + sessionName.hashCode();
+ }
+
+ /**
+ * Returns whether or not this service address is malformed.
+ *
+ * @return True if malformed.
+ */
+ public boolean isMalformed() {
+ return sessionName == null || connectionSpec.malformed();
+ }
+
+ /**
+ * Returns the name of the remove service.
+ *
+ * @return The service name.
+ */
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ /**
+ * Returns the name of the remote session.
+ *
+ * @return The session name.
+ */
+ public String getSessionName() {
+ return sessionName;
+ }
+
+ /**
+ * Returns the connection spec for the remote service.
+ *
+ * @return The connection spec.
+ */
+ public Spec getConnectionSpec() {
+ return connectionSpec;
+ }
+
+ /**
+ * Sets the RPC target to be used when communicating with the remove service.
+ *
+ * @param target The target to set.
+ */
+ public void setTarget(RPCTarget target) {
+ this.target = target;
+ }
+
+ /**
+ * Returns the RPC target to be used when communicating with the remove service.
+ *
+ * @return The target to use.
+ */
+ public RPCTarget getTarget() {
+ return target;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java
new file mode 100755
index 00000000000..fa3b2b2b260
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java
@@ -0,0 +1,84 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Class used to reuse services for the same address when sending messages over the rpc network.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RPCServicePool {
+
+ private final RPCNetwork net;
+ private final ThreadLocalCache services = new ThreadLocalCache();
+ private final int maxSize;
+
+ /**
+ * Create a new service pool for the given network.
+ *
+ * @param net The underlying RPC network.
+ * @param maxSize The max number of services to cache.
+ */
+ public RPCServicePool(RPCNetwork net, int maxSize) {
+ this.net = net;
+ this.maxSize = maxSize;
+ }
+
+ /**
+ * Returns the RPCServiceAddress that corresponds to a given pattern. This reuses the RPCService object for matching
+ * pattern so that load balancing is possible on the network level.
+ *
+ * @param pattern The pattern for the service we require.
+ * @return A service address for the given pattern.
+ */
+ public RPCServiceAddress resolve(String pattern) {
+ RPCService service = services.get().get(pattern);
+ if (service == null) {
+ service = new RPCService(net.getMirror(), pattern);
+ services.get().put(pattern, service);
+ }
+ return service.resolve();
+ }
+
+ /**
+ * Returns the number of services available in the pool. This number will never exceed the limit given at
+ * construction time.
+ *
+ * @return The current size of this pool.
+ */
+ public int getSize() {
+ return services.get().size();
+ }
+
+ /**
+ * Returns whether or not there is a service available in the pool the corresponds to the given pattern.
+ *
+ * @param pattern The pattern to check for.
+ * @return True if a corresponding service is in the pool.
+ */
+ public boolean hasService(String pattern) {
+ return services.get().containsKey(pattern);
+ }
+
+ private class ThreadLocalCache extends ThreadLocal<ServiceLRUCache> {
+
+ @Override
+ protected ServiceLRUCache initialValue() {
+ return new ServiceLRUCache();
+ }
+ }
+
+ private class ServiceLRUCache extends LinkedHashMap<String, RPCService> {
+
+ ServiceLRUCache() {
+ super(16, 0.75f, true);
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<String, RPCService> entry) {
+ return size() > maxSize;
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTarget.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTarget.java
new file mode 100755
index 00000000000..b7cd1249dda
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTarget.java
@@ -0,0 +1,173 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.component.Version;
+import com.yahoo.jrt.*;
+import com.yahoo.log.LogLevel;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+/**
+ * <p>Implements a target object that encapsulates the JRT connection
+ * target. Instances of this class are returned by {@link RPCService}, and
+ * cached by {@link RPCTargetPool}.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RPCTarget implements RequestWaiter {
+
+ private static final Logger log = Logger.getLogger(RPCTarget.class.getName());
+ private final AtomicInteger ref = new AtomicInteger(1);
+ private final String name;
+ private final Target target;
+ private boolean targetInvoked = false;
+ private Version version = null;
+ private List<VersionHandler> versionHandlers = new LinkedList<>();
+
+ /**
+ * <p>Constructs a new instance of this class.</p>
+ *
+ * @param spec The connection spec of this target.
+ * @param orb The jrt supervisor to use when connecting to target.
+ */
+ public RPCTarget(Spec spec, Supervisor orb) {
+ this.name = spec.toString();
+ this.target = orb.connect(spec);
+ }
+
+ /**
+ * <p>Returns the encapsulated JRT target.</p>
+ *
+ * @return The target.
+ */
+ public Target getJRTTarget() {
+ return target;
+ }
+
+ /**
+ * <p>This method is used for explicit reference counting targets to allow
+ * reusing open connections. An instance of this class is constructed with a
+ * single reference.</p>
+ *
+ * @see #subRef()
+ */
+ public void addRef() {
+ ref.incrementAndGet();
+ }
+
+ /**
+ * <p>This method is used for explicit reference counting targets to allow
+ * reusing open connections. When the reference count reaches 0, the
+ * connection is closed.</p>
+ *
+ * @see #addRef()
+ */
+ public void subRef() {
+ if (ref.decrementAndGet() == 0) {
+ target.close();
+ }
+ }
+
+ /**
+ * <p>Returns the current reference count of this target. If this ever
+ * returns 0 it means the underlying connection is closed and invalid.</p>
+ *
+ * @return The number of references in use.
+ */
+ public int getRefCount() {
+ return ref.get();
+ }
+
+ /**
+ * <p>Requests the version of this target be passed to the given {@link
+ * VersionHandler}. If the version is available, the handler is called
+ * synchronously; if not, the handler is called by the network thread once
+ * the target responds to the version query.</p>
+ *
+ * @param timeout The timeout for the request in seconds.
+ * @param handler The handler to be called once the version is available.
+ */
+ public void resolveVersion(double timeout, VersionHandler handler) {
+ boolean hasVersion = false;
+ boolean shouldInvoke = false;
+ boolean shouldLog = log.isLoggable(LogLevel.DEBUG);
+ synchronized (this) {
+ if (version != null) {
+ if (shouldLog) {
+ log.log(LogLevel.DEBUG, "Version already available for target '" + name + "' (version " + version + ").");
+ }
+ hasVersion = true;
+ } else {
+ if (shouldLog) {
+ log.log(LogLevel.DEBUG, "Registering version handler '" + handler + "' for target '" + name + "'.");
+ }
+ versionHandlers.add(handler);
+ if (!targetInvoked) {
+ targetInvoked = true;
+ shouldInvoke = true;
+ }
+ }
+ }
+ if (hasVersion) {
+ handler.handleVersion(version);
+ } else if (shouldInvoke) {
+ if (shouldLog) {
+ log.log(LogLevel.DEBUG, "Invoking mbus.getVersion() on target '" + name + "'");
+ }
+ Request req = new Request("mbus.getVersion");
+ target.invokeAsync(req, timeout, this);
+ }
+ }
+
+ @Override
+ public void handleRequestDone(Request req) {
+ List<VersionHandler> handlers;
+ boolean shouldLog = log.isLoggable(LogLevel.DEBUG);
+ synchronized (this) {
+ targetInvoked = false;
+ if (req.checkReturnTypes("s")) {
+ String str = req.returnValues().get(0).asString();
+ try {
+ version = new Version(str);
+ if (shouldLog) {
+ log.log(LogLevel.DEBUG, "Target '" + name + "' has version " + version + ".");
+ }
+ } catch (IllegalArgumentException e) {
+ log.log(LogLevel.WARNING, "Failed to parse '" + str + "' as version for target '" + name + "'.", e);
+ }
+ } else {
+ log.log(LogLevel.INFO, "Method mbus.getVersion() failed for target '" + name + "'; " +
+ req.errorMessage());
+ }
+ handlers = versionHandlers;
+ versionHandlers = new LinkedList<>();
+ }
+ for (VersionHandler handler : handlers) {
+ handler.handleVersion(version);
+ }
+ }
+
+ /**
+ * <p>Declares a version handler used when resolving the version of a
+ * target. An instance of this is passed to {@link
+ * RPCTarget#resolveVersion(double,
+ * com.yahoo.messagebus.network.rpc.RPCTarget.VersionHandler)}, and invoked
+ * either synchronously or asynchronously, depending on whether or not the
+ * version is already available.</p>
+ */
+ public interface VersionHandler {
+
+ /**
+ * <p>This method is invoked once the version of the corresponding
+ * {@link RPCTarget} becomes available. If a problem occured while
+ * retrieving the version, this method is invoked with a null
+ * argument.</p>
+ *
+ * @param ver The version of corresponding target, or null.
+ */
+ public void handleVersion(Version ver);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTargetPool.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTargetPool.java
new file mode 100755
index 00000000000..a695060649a
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCTargetPool.java
@@ -0,0 +1,131 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.concurrent.SystemTimer;
+import com.yahoo.concurrent.Timer;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Class used to reuse targets for the same address when sending messages over the rpc network.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class RPCTargetPool {
+
+ private final Map<String, Entry> targets = new HashMap<String, Entry>();
+ private final Timer timer;
+ private final long expireMillis;
+
+ /**
+ * Constructs a new instance of this class, and registers the {@link SystemTimer} for detecting and closing
+ * connections that have expired according to the given parameter.
+ *
+ * @param expireSecs The number of seconds until an idle connection is closed.
+ */
+ public RPCTargetPool(double expireSecs) {
+ this(SystemTimer.INSTANCE, expireSecs);
+ }
+
+ /**
+ * Constructs a new instance of this class, using the given {@link Timer} for detecting and closing connections that
+ * have expired according to the second paramter.
+ *
+ * @param timer The timer to use for connection expiration.
+ * @param expireSecs The number of seconds until an idle connection is closed.
+ */
+ public RPCTargetPool(Timer timer, double expireSecs) {
+ this.timer = timer;
+ this.expireMillis = (long)(expireSecs * 1000);
+ }
+
+ /**
+ * Closes all unused target connections. Unless the force argument is true, this method will allow a grace period
+ * for all connections after last use before it starts closing them. This allows the most recently used connections
+ * to stay open.
+ *
+ * @param force Whether or not to force flush.
+ */
+ public synchronized void flushTargets(boolean force) {
+ Iterator<Entry> it = targets.values().iterator();
+ long currentTime = timer.milliTime();
+ long expireTime = currentTime - expireMillis;
+ while (it.hasNext()) {
+ Entry entry = it.next();
+ RPCTarget target = entry.target;
+ if (target.getJRTTarget().isValid()) {
+ if (target.getRefCount() > 1) {
+ entry.lastUse = currentTime;
+ continue; // someone is using this
+ }
+ if (!force) {
+ if (entry.lastUse > expireTime) {
+ continue; // not sufficiently idle
+ }
+ }
+ }
+ target.subRef();
+ it.remove();
+ }
+ }
+
+ /**
+ * This method will return a target for the given address. If a target does not currently exist for the given
+ * address, it will be created and added to the internal map. Each target is also reference counted so that an
+ * active target is never expired.
+ *
+ * @param orb The supervisor to use to connect to the target.
+ * @param address The address to resolve to a target.
+ * @return A target for the given address.
+ */
+ public RPCTarget getTarget(Supervisor orb, RPCServiceAddress address) {
+ Spec spec = address.getConnectionSpec();
+ String key = spec.toString();
+ RPCTarget ret;
+ synchronized (this) {
+ Entry entry = targets.get(key);
+ if (entry != null) {
+ if (entry.target.getJRTTarget().isValid()) {
+ entry.target.addRef();
+ entry.lastUse = timer.milliTime();
+ return entry.target;
+ }
+ entry.target.subRef();
+ targets.remove(key);
+ }
+ ret = new RPCTarget(spec, orb);
+ targets.put(key, new Entry(ret, timer.milliTime()));
+ }
+ ret.addRef();
+ return ret;
+ }
+
+
+ /**
+ * Returns the number of targets currently contained in this.
+ *
+ * @return The size of the internal map.
+ */
+ public synchronized int size() {
+ return targets.size();
+ }
+
+ /**
+ * Implements a helper class holds the necessary reference and timestamp of a target. The lastUse member is updated
+ * when a call to {@link RPCTargetPool#flushTargets(boolean)} iterates over an active target.
+ */
+ private static class Entry {
+
+ final RPCTarget target;
+ long lastUse = 0;
+
+ Entry(RPCTarget target, long lastUse) {
+ this.target = target;
+ this.lastUse = lastUse;
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/SlobrokConfigSubscriber.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/SlobrokConfigSubscriber.java
new file mode 100755
index 00000000000..0c7e8b4bbbd
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/SlobrokConfigSubscriber.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.config.subscription.ConfigSubscriber;
+import com.yahoo.jrt.slobrok.api.SlobrokList;
+import com.yahoo.cloud.config.SlobroksConfig;
+
+/**
+ * This class implements subscription to slobrok config.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SlobrokConfigSubscriber implements ConfigSubscriber.SingleSubscriber<SlobroksConfig>{
+
+ private SlobrokList slobroks = new SlobrokList();
+ private ConfigSubscriber subscriber;
+
+ /**
+ * Constructs a new config subscriber for a given config id.
+ *
+ * @param configId The id of the config to subscribe to.
+ */
+ public SlobrokConfigSubscriber(String configId) {
+ subscriber = new ConfigSubscriber();
+ subscriber.subscribe(this, SlobroksConfig.class, configId);
+ }
+
+ public SlobrokConfigSubscriber(SlobroksConfig slobroksConfig) {
+ configure(slobroksConfig);
+ }
+
+ @Override
+ public void configure(SlobroksConfig config) {
+ String[] list = new String[config.slobrok().size()];
+ for(int i = 0; i < config.slobrok().size(); i++) {
+ list[i] = config.slobrok(i).connectionspec();
+ }
+ slobroks.setup(list);
+ }
+
+ /**
+ * Returns the current slobroks config as an array of connection spec strings.
+ *
+ * @return The slobroks config.
+ */
+ public SlobrokList getSlobroks() {
+ return new SlobrokList(slobroks);
+ }
+
+ /**
+ * Shuts down the config subscription by unsubscribing to the slobroks config.
+ */
+ public void shutdown() {
+ if (subscriber != null) {
+ subscriber.close();
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/package-info.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/package-info.java
new file mode 100644
index 00000000000..ac7f349acd7
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * This package contains an RPC implementation of the Network interface declared in the com.yahoo.messagebus.network package.
+ */
+@ExportPackage
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSServer.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSServer.java
new file mode 100755
index 00000000000..ad4d21ce171
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSServer.java
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc.test;
+
+import com.yahoo.jrt.*;
+import com.yahoo.jrt.slobrok.api.SlobrokList;
+import com.yahoo.jrt.slobrok.api.Register;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OOSServer {
+ private int getCnt = 1;
+ private List<String> state = new ArrayList<String>();
+ private Supervisor orb;
+ private Register register;
+ private Acceptor listener;
+
+ public OOSServer(Slobrok slobrok, String service, OOSState state) {
+ orb = new Supervisor(new Transport());
+ orb.addMethod(new Method("fleet.getOOSList", "ii", "Si",
+ new MethodHandler() {
+ public void invoke(Request request) {
+ rpc_poll(request);
+ }
+ })
+ .methodDesc("Fetch OOS information.")
+ .paramDesc(0, "gencnt", "Generation already known by client.")
+ .paramDesc(1, "timeout", "How many milliseconds to wait for changes before returning if nothing has changed (max=10000).")
+ .returnDesc(0, "names", "List of services that are OOS (empty if generation has not changed).")
+ .returnDesc(1, "newgen", "Generation of the returned list."));
+ try {
+ listener = orb.listen(new Spec(0));
+ }
+ catch (ListenFailedException e) {
+ orb.transport().shutdown().join();
+ throw new RuntimeException(e);
+ }
+ SlobrokList slist = new SlobrokList();
+ slist.setup(new String[] { new Spec("localhost", slobrok.port()).toString() });
+ register = new Register(orb, slist, "localhost", listener.port());
+ register.registerName(service);
+ setState(state);
+ }
+
+ public void shutdown() {
+ register.shutdown();
+ listener.shutdown().join();
+ orb.transport().shutdown().join();
+ }
+
+ public void setState(OOSState state) {
+ List<String> newState = new ArrayList<String>();
+ for (String service : state.getServices()) {
+ if (state.isOOS(service)) {
+ newState.add(service);
+ }
+ }
+ synchronized(this) {
+ this.state = newState;
+ if (++getCnt == 0) {
+ getCnt = 1;
+ }
+ }
+ }
+
+ private void rpc_poll(Request request) {
+ synchronized(this) {
+ request.returnValues()
+ .add(new StringArray(state.toArray(new String[state.size()])))
+ .add(new Int32Value(getCnt));
+ }
+ }
+
+ public int getPort() {
+ return listener.port();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSState.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSState.java
new file mode 100755
index 00000000000..912caf45691
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/OOSState.java
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc.test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OOSState {
+ private Map<String, Boolean> data = new LinkedHashMap<String, Boolean>();
+
+ public OOSState add(String service, boolean oos) {
+ data.put(service, oos);
+ return this;
+ }
+
+ public Set<String> getServices() {
+ return data.keySet();
+ }
+
+ public boolean isOOS(String service) {
+ return data.containsKey(service) && data.get(service);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/SlobrokState.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/SlobrokState.java
new file mode 100755
index 00000000000..b0865b7618e
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/SlobrokState.java
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc.test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SlobrokState {
+ private Map<String, Integer> data = new LinkedHashMap<String, Integer>();
+
+ public SlobrokState add(String pattern, int count) {
+ data.put(pattern, count);
+ return this;
+ }
+
+ public Set<String> getPatterns() {
+ return data.keySet();
+ }
+
+ public int getCount(String pattern) {
+ return data.containsKey(pattern) ? data.get(pattern) : 1;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/TestServer.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/TestServer.java
new file mode 100644
index 00000000000..683b986bb0d
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/TestServer.java
@@ -0,0 +1,211 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc.test;
+
+import com.yahoo.component.Version;
+import com.yahoo.component.Vtag;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.slobrok.api.Mirror;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.log.LogLevel;
+import com.yahoo.messagebus.MessageBus;
+import com.yahoo.messagebus.MessageBusParams;
+import com.yahoo.messagebus.Protocol;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetwork;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.routing.RoutingSpec;
+import com.yahoo.messagebus.routing.RoutingTableSpec;
+import com.yahoo.messagebus.test.SimpleProtocol;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+/**
+ * A simple test server implementation.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class TestServer {
+
+ private static final Logger log = Logger.getLogger(TestServer.class.getName());
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ public final VersionedRPCNetwork net;
+ public final MessageBus mb;
+
+ /**
+ * Create a new test server.
+ *
+ * @param name The service name prefix for this server.
+ * @param table The routing table spec to be used, may be null for no routing.
+ * @param slobrok The slobrok to register with (local).
+ * @param oosServerPattern the string pattern for oos servers, may be null for deactivate.
+ * @param protocol The protocol that this server should support in addition to SimpleProtocol.
+ */
+ public TestServer(String name, RoutingTableSpec table, Slobrok slobrok, String oosServerPattern, Protocol protocol) {
+ this(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams()
+ .setIdentity(new Identity(name))
+ .setSlobrokConfigId(getSlobrokConfig(slobrok))
+ .setOOSServerPattern(oosServerPattern));
+ if (protocol != null) {
+ mb.putProtocol(protocol);
+ }
+ if (table != null) {
+ setupRouting(table);
+ }
+ }
+
+ /**
+ * Create a new test server.
+ *
+ * @param mbusParams The parameters for mesasge bus.
+ * @param netParams The parameters for the rpc network.
+ */
+ public TestServer(MessageBusParams mbusParams, RPCNetworkParams netParams) {
+ net = new VersionedRPCNetwork(netParams);
+ mb = new MessageBus(net, mbusParams);
+ }
+
+ // Overrides Object.
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (destroy()) {
+ log.log(LogLevel.WARNING, "TestServer destroyed by finalizer, please review application shutdown logic.");
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Sets the destroyed flag to true. The very first time this method is called, it cleans up all its dependencies.
+ * Even if you retain a reference to this object, all of its content is allowed to be garbage collected.
+ *
+ * @return True if content existed and was destroyed.
+ */
+ public boolean destroy() {
+ if (!destroyed.getAndSet(true)) {
+ mb.destroy();
+ net.destroy();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the raw config needed to connect to the given slobrok.
+ *
+ * @param slobrok The slobrok whose connection spec to include.
+ * @return The raw config string.
+ */
+ public static String getSlobrokConfig(Slobrok slobrok) {
+ return "raw:slobrok[1]\n" +
+ "slobrok[0].connectionspec \"" + new Spec("localhost", slobrok.port()).toString() + "\"\n";
+ }
+
+ /**
+ * Proxies the {@link MessageBus#setupRouting(RoutingSpec)} method by encapsulating the given table specification
+ * within the required {@link RoutingSpec}.
+ *
+ * @param table The table to configure.
+ */
+ public void setupRouting(RoutingTableSpec table) {
+ mb.setupRouting(new RoutingSpec().addTable(table));
+ }
+
+ /**
+ * Wait for some pattern to resolve to some number of services.
+ *
+ * @param pattern Pattern to lookup in slobrok.
+ * @param cnt Number of services it must resolve to.
+ * @return Whether or not the required state was reached.
+ */
+ public boolean waitSlobrok(String pattern, int cnt) {
+ return waitState(new SlobrokState().add(pattern, cnt));
+ }
+
+ /**
+ * Wait for a required slobrok state.
+ *
+ * @param slobrokState The state to wait for.
+ * @return Whether or not the required state was reached.
+ */
+ public boolean waitState(SlobrokState slobrokState) {
+ for (int i = 0; i < 6000 && !Thread.currentThread().isInterrupted(); ++i) {
+ boolean done = true;
+ for (String pattern : slobrokState.getPatterns()) {
+ Mirror.Entry[] res = net.getMirror().lookup(pattern);
+ if (res.length != slobrokState.getCount(pattern)) {
+ done = false;
+ }
+ }
+ if (done) {
+ return true;
+ }
+ try {
+ Thread.sleep(10);
+ }
+ catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Wait for some service to go out-of-service.
+ *
+ * @param service The service to wait for.
+ * @return Whether or not the service went out-of-service.
+ */
+ public boolean waitOOS(String service) {
+ return waitState(new OOSState().add(service, true));
+ }
+
+ /**
+ * Wait for a required OOS state.
+ *
+ * @param oosState The state to wait for.
+ * @return Whether or not the required state was reached.
+ */
+ public boolean waitState(OOSState oosState) {
+ for (int i = 0; i < 1000 && !Thread.currentThread().isInterrupted(); ++i) {
+ boolean done = true;
+ for (String service : oosState.getServices()) {
+ if (net.getOOSManager().isOOS(service) != oosState.isOOS(service)) {
+ done = false;
+ }
+ }
+ if (done) {
+ return true;
+ }
+ try {
+ Thread.sleep(10);
+ }
+ catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ return false;
+ }
+
+ public static class VersionedRPCNetwork extends RPCNetwork {
+
+ private Version version = Vtag.currentVersion;
+
+ public VersionedRPCNetwork(RPCNetworkParams netParams) {
+ super(netParams);
+ }
+
+ @Override
+ protected Version getVersion() {
+ return version;
+ }
+
+ public void setVersion(Version version) {
+ this.version = version;
+ flushTargetPool();
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/package-info.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/package-info.java
new file mode 100644
index 00000000000..511e1df4a8a
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/test/package-info.java
@@ -0,0 +1,6 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * This package contains utility classes for the unit tests in the com.yahoo.messagebus.network.rpc package.
+ */
+@com.yahoo.api.annotations.PackageMarker
+package com.yahoo.messagebus.network.rpc.test;
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/package-info.java b/messagebus/src/main/java/com/yahoo/messagebus/package-info.java
new file mode 100644
index 00000000000..b6e05b4bd28
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/package-info.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * This package contains the main API of the message bus. The typical user will instantiate
+ * {@link com.yahoo.messagebus.RPCMessageBus}.
+ */
+@ExportPackage
+@PublicApi
+package com.yahoo.messagebus;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/ApplicationSpec.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/ApplicationSpec.java
new file mode 100755
index 00000000000..47f688e0f51
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/ApplicationSpec.java
@@ -0,0 +1,131 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * This class holds the specifications of an application running message bus services. It is used for ensuring that a
+ * {@link RoutingSpec} holds valid routing specifications.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ApplicationSpec {
+
+ private final HashMap<String, HashSet<String>> services = new HashMap<String, HashSet<String>>();
+
+ /**
+ * Constructs a new instance of this class.
+ */
+ public ApplicationSpec() {
+ // empty
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param obj The object to copy.
+ */
+ public ApplicationSpec(ApplicationSpec obj) {
+ add(obj);
+ }
+
+ /**
+ * Adds the content of the given application to this.
+ *
+ * @param app The application whose content to copy.
+ * @return This, to allow chaining.
+ */
+ public ApplicationSpec add(ApplicationSpec app) {
+ for (Map.Entry<String, HashSet<String>> entry : app.services.entrySet()) {
+ String protocol = entry.getKey();
+ for (String service : entry.getValue()) {
+ addService(protocol, service);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Adds a service name to the list of known services.
+ *
+ * @param protocol The protocol for which to add the service.
+ * @param name The service to add.
+ * @return This, to allow chaining.
+ */
+ public ApplicationSpec addService(String protocol, String name) {
+ if (!services.containsKey(protocol)) {
+ services.put(protocol, new HashSet<String>());
+ }
+ services.get(protocol).add(name);
+ return this;
+ }
+
+ /**
+ * Determines whether or not the given service pattern matches any of the known services.
+ *
+ * @param protocol The protocol whose services to check.
+ * @param pattern The pattern to match.
+ * @return True if at least one service was found.
+ */
+ public boolean isService(String protocol, String pattern) {
+ if (services.containsKey(protocol)) {
+ Pattern regex = toRegex(pattern);
+ for (String service : services.get(protocol)) {
+ if (regex.matcher(service).find()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Converts the given string pattern to a usable regex pattern object.
+ *
+ * @param pattern The string pattern to convert.
+ * @return The corresponding regex pattern object.
+ */
+ private static Pattern toRegex(String pattern) {
+ StringBuilder ret = new StringBuilder();
+ ret.append("^");
+ for (int i = 0; i < pattern.length(); i++) {
+ ret.append(toRegex(pattern.charAt(i)));
+ }
+ ret.append("$");
+ return Pattern.compile(ret.toString());
+ }
+
+ /**
+ * Converts a single string pattern char to a regex string. This method is invoked by {@link #toRegex(String)} once
+ * for each character in the string pattern.
+ *
+ * @param c The character to convert.
+ * @return The corresponding regex pattern string.
+ */
+ private static String toRegex(char c) {
+ switch (c) {
+ case '*':
+ return ".*";
+ case '?':
+ return ".";
+ case '^':
+ case '$':
+ case '|':
+ case '{':
+ case '}':
+ case '(':
+ case ')':
+ case '[':
+ case ']':
+ case '\\':
+ case '+':
+ case '.':
+ return "\\" + c;
+ default:
+ return "" + c;
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/ErrorDirective.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/ErrorDirective.java
new file mode 100755
index 00000000000..6087a795c2b
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/ErrorDirective.java
@@ -0,0 +1,63 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * This class represents an error directive within a {@link Hop}'s selector. This means to stop whatever is being
+ * resolved, and instead return a reply containing a specified error.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ErrorDirective implements HopDirective {
+
+ private String msg;
+
+ /**
+ * Constructs a new error directive.
+ *
+ * @param msg The error message.
+ */
+ public ErrorDirective(String msg) {
+ this.msg = msg;
+ }
+
+ /**
+ * Returns the error string that is to be assigned to the reply.
+ *
+ * @return The error string.
+ */
+ public String getMessage() {
+ return msg;
+ }
+
+ @Override
+ public boolean matches(HopDirective dir) {
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ErrorDirective)) {
+ return false;
+ }
+ ErrorDirective rhs = (ErrorDirective)obj;
+ if (!msg.equals(rhs.msg)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + msg + ")";
+ }
+
+ @Override
+ public String toDebugString() {
+ return "ErrorDirective(msg = '" + msg + "')";
+ }
+
+ @Override
+ public int hashCode() {
+ return msg != null ? msg.hashCode() : 0;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/Hop.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/Hop.java
new file mode 100755
index 00000000000..25bcd062e36
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/Hop.java
@@ -0,0 +1,291 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>Hops are the components of routes.
+ * They are instantiated from a {@link HopBlueprint} or
+ * using the factory method {@link #parse(String)}. A hop is resolved to a recipient, from
+ * a set of primitives, either a string primitive that is to be matched verbatim to a
+ * service address, or a {@link RoutingPolicy} directive.</p>
+ *
+ * @author bratseth
+ */
+public class Hop {
+
+ private final List<HopDirective> selector = new ArrayList<>();
+ private boolean ignoreResult = false;
+ private String cache = null;
+
+ /**
+ * <p>Constructs an empty hop. You will need to add directives to the
+ * selector to make this usable.</p>
+ */
+ public Hop() {
+ // empty
+ }
+
+ /**
+ * <p>Implements the copy constructor.</p>
+ *
+ * @param hop The hop to copy.
+ */
+ public Hop(Hop hop) {
+ selector.addAll(hop.selector);
+ ignoreResult = hop.ignoreResult;
+ }
+
+ /**
+ * <p>Constructs a fully populated hop. This is package private and used by
+ * the {@link HopBlueprint#create()} method.</p>
+ *
+ * @param selector The selector to copy.
+ * @param ignoreResult Whether or not to ignore the result of this hop.
+ */
+ Hop(List<HopDirective> selector, boolean ignoreResult) {
+ this.selector.addAll(selector);
+ this.ignoreResult = ignoreResult;
+ }
+
+ /**
+ * <p>Parses the given string as a single hop. The {@link #toString()}
+ * method is compatible with this parser.</p>
+ *
+ * @param str The string to parse.
+ * @return A hop that corresponds to the string.
+ */
+ public static Hop parse(String str) {
+ Route route = Route.parse(str);
+ if (route.getNumHops() > 1) {
+ return new Hop().addDirective(new ErrorDirective("Failed to completely parse '" + str + "'."));
+ }
+ return route.getHop(0);
+ }
+
+ /**
+ * <p>Returns whether or not there are any directives contained in this
+ * hop.</p>
+ *
+ * @return True if there is at least one directive.
+ */
+ public boolean hasDirectives() {
+ return !selector.isEmpty();
+ }
+
+ /**
+ * <p>Returns the number of directives contained in this hop.</p>
+ *
+ * @return The number of directives.
+ */
+ public int getNumDirectives() {
+ return selector.size();
+ }
+
+ /**
+ * <p>Returns the directive at the given index.</p>
+ *
+ * @param i The index of the directive to return.
+ * @return The item.
+ */
+ public HopDirective getDirective(int i) {
+ return selector.get(i);
+ }
+
+ /**
+ * <p>Adds a new directive to this hop.</p>
+ *
+ * @param directive The directive to add.
+ * @return This, to allow chaining.
+ */
+ public Hop addDirective(HopDirective directive) {
+ cache = null;
+ selector.add(directive);
+ return this;
+ }
+
+ /**
+ * <p>Sets the directive at a given index.</p>
+ *
+ * @param i The index at which to set the directive.
+ * @param directive The directive to set.
+ * @return This, to allow chaining.
+ */
+ public Hop setDirective(int i, HopDirective directive) {
+ selector.set(i, directive);
+ return this;
+ }
+
+ /**
+ * <p>Removes the directive at the given index.</p>
+ *
+ * @param i The index of the directive to remove.
+ * @return The removed directive.
+ */
+ public HopDirective removeDirective(int i) {
+ cache = null;
+ return selector.remove(i);
+ }
+
+ /**
+ * <p>Clears all directives from this hop.</p>
+ *
+ * @return This, to allow chaining.
+ */
+ public Hop clearDirectives() {
+ cache = null;
+ selector.clear();
+ return this;
+ }
+
+ /**
+ * <p>Returns whether or not to ignore the result when routing through this
+ * hop.</p>
+ *
+ * @return True to ignore the result.
+ */
+ public boolean getIgnoreResult() {
+ return ignoreResult;
+ }
+
+ /**
+ * <p>Sets whether or not to ignore the result when routing through this
+ * hop.</p>
+ *
+ * @param ignoreResult Whether or not to ignore the result.
+ * @return This, to allow chaining.
+ */
+ public Hop setIgnoreResult(boolean ignoreResult) {
+ this.ignoreResult = ignoreResult;
+ return this;
+ }
+
+ /**
+ * <p>Returns true whether this hop matches another. This respects policy
+ * directives matching any other.</p>
+ *
+ * @param hop The hop to compare to.
+ * @return True if this matches the argument, false otherwise.
+ */
+ public boolean matches(Hop hop) {
+ if (hop == null || hop.getNumDirectives() != selector.size()) {
+ return false;
+ }
+ for (int i = 0; i < hop.getNumDirectives(); ++i) {
+ if (!selector.get(i).matches(hop.getDirective(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Returns a string representation of this that can be debugged but not
+ * parsed.</p>
+ *
+ * @return The debug string.
+ */
+ public String toDebugString() {
+ StringBuilder ret = new StringBuilder("Hop(selector = { ");
+ for (int i = 0; i < selector.size(); ++i) {
+ ret.append(selector.get(i).toDebugString());
+ if (i < selector.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" }, ignoreResult = ").append(ignoreResult).append(")");
+ return ret.toString();
+ }
+
+ @Override
+ public String toString() {
+ return (ignoreResult ? "?" : "") + getServiceName();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Hop)) {
+ return false;
+ }
+ Hop rhs = (Hop)obj;
+ if (selector.size() != rhs.selector.size()) {
+ return false;
+ }
+ for (int i = 0; i < selector.size(); ++i) {
+ if (!selector.get(i).equals(rhs.selector.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Returns the service name referenced by this hop. This is the
+ * concatenation of all selector primitives, but with no ignore-result
+ * prefix.</p>
+ *
+ * @return The service name.
+ */
+ public String getServiceName() {
+ if (cache == null) {
+ cache = toString(0, selector.size());
+ }
+ return cache;
+ }
+
+ /**
+ * <p>Returns a string concatenation of a subset of the selector primitives
+ * contained in this.</p>
+ *
+ * @param fromIncluding The index of the first primitive to include.
+ * @param toNotIncluding The index after the last primitive to include.
+ * @return The string concatenation.
+ */
+ public String toString(int fromIncluding, int toNotIncluding) {
+ StringBuilder ret = new StringBuilder();
+ for (int i = fromIncluding; i < toNotIncluding; ++i) {
+ ret.append(selector.get(i));
+ if (i < toNotIncluding - 1) {
+ ret.append("/");
+ }
+ }
+ return ret.toString();
+ }
+
+ /**
+ * <p>Returns the prefix of this hop's selector to, but not including, the
+ * given index.</p>
+ *
+ * @param toNotIncluding The index to which to generate prefix.
+ * @return The prefix before the index.
+ */
+ public String getPrefix(int toNotIncluding) {
+ if (toNotIncluding > 0) {
+ return toString(0, toNotIncluding) + "/";
+ }
+ return "";
+ }
+
+ /**
+ * <p>Returns the suffix of this hop's selector from, but not including, the
+ * given index.</p>
+ *
+ * @param fromNotIncluding The index from which to generate suffix.
+ * @return The suffix after the index.
+ */
+ public String getSuffix(int fromNotIncluding) {
+ if (fromNotIncluding < selector.size() - 1) {
+ return "/" + toString(fromNotIncluding + 1, selector.size());
+ }
+ return "";
+ }
+
+ @Override
+ public int hashCode() {
+ int result = selector != null ? selector.hashCode() : 0;
+ result = 31 * result + (ignoreResult ? 1 : 0);
+ result = 31 * result + (cache != null ? cache.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/HopBlueprint.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/HopBlueprint.java
new file mode 100755
index 00000000000..66321b5975e
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/HopBlueprint.java
@@ -0,0 +1,144 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.*;
+
+/**
+ * A hop blueprint is a stored prototype of a hop that has been created from a {@link HopSpec} object. A map of these
+ * are stored in a {@link RoutingTable}.
+ *
+ * @author <a href="bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class HopBlueprint {
+
+ private final List<HopDirective> selector = new ArrayList<>();
+ private final List<Hop> recipients = new ArrayList<>();
+ private boolean ignoreResult = false;
+
+ /**
+ * The default constructor requires valid arguments for all member variables.
+ *
+ * @param spec The spec of this rule.
+ */
+ HopBlueprint(HopSpec spec) {
+ Hop hop = Hop.parse(spec.getSelector());
+ for (int i = 0; i < hop.getNumDirectives(); ++i) {
+ selector.add(hop.getDirective(i));
+ }
+ List<String> lst = new ArrayList<>();
+ for (int i = 0; i < spec.getNumRecipients(); ++i) {
+ lst.add(spec.getRecipient(i));
+ }
+ for (String recipient : lst) {
+ recipients.add(Hop.parse(recipient));
+ }
+ ignoreResult = spec.getIgnoreResult();
+ }
+
+ /**
+ * Creates a hop instance from thie blueprint.
+ *
+ * @return The created hop.
+ */
+ public Hop create() {
+ return new Hop(selector, ignoreResult);
+ }
+
+ /**
+ * Returns whether or not there are any directives contained in this hop.
+ *
+ * @return True if there is at least one directive.
+ */
+ public boolean hasDirectives() {
+ return !selector.isEmpty();
+ }
+
+ /**
+ * Returns the number of directives contained in this hop.
+ *
+ * @return The number of directives.
+ */
+ public int getNumDirectives() {
+ return selector.size();
+ }
+
+ /**
+ * Returns the directive at the given index.
+ *
+ * @param i The index of the directive to return.
+ * @return The item.
+ */
+ public HopDirective getDirective(int i) {
+ return selector.get(i);
+ }
+
+ /**
+ * Returns whether or not there are any recipients that the selector can choose from.
+ *
+ * @return True if there is at least one recipient.
+ */
+ public boolean hasRecipients() {
+ return !recipients.isEmpty();
+ }
+
+ /**
+ * Returns the number of recipients that the selector can choose from.
+ *
+ * @return The number of recipients.
+ */
+ public int getNumRecipients() {
+ return recipients.size();
+ }
+
+ /**
+ * Returns the recipient at the given index.
+ *
+ * @param i The index of the recipient to return.
+ * @return The recipient at the given index.
+ */
+ public Hop getRecipient(int i) {
+ return recipients.get(i);
+ }
+
+ /**
+ * Returns whether or not to ignore the result when routing through this hop.
+ *
+ * @return True to ignore the result.
+ */
+ public boolean getIgnoreResult() {
+ return ignoreResult;
+ }
+
+ /**
+ * Sets whether or not to ignore the result when routing through this hop.
+ *
+ * @param ignoreResult Whether or not to ignore the result.
+ * @return This, to allow chaining.
+ */
+ public HopBlueprint setIgnoreResult(boolean ignoreResult) {
+ this.ignoreResult = ignoreResult;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder("HopBlueprint(selector = { ");
+ for (int i = 0; i < selector.size(); ++i) {
+ ret.append("'").append(selector.get(i)).append("'");
+ if (i < selector.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" }, recipients = { ");
+ for (int i = 0; i < recipients.size(); ++i) {
+ ret.append("'").append(recipients.get(i)).append("'");
+ if (i < recipients.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" }, ignoreResult = ").append(ignoreResult).append(")");
+ return ret.toString();
+ }
+}
+
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/HopDirective.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/HopDirective.java
new file mode 100755
index 00000000000..9623b45ca20
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/HopDirective.java
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * This class is the base class for the primitives that make up a {@link Hop}'s selector.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface HopDirective {
+
+ /**
+ * Returns true if this directive matches another.
+ *
+ * @param dir The directive to compare this to.
+ * @return True if this matches the argument.
+ */
+ public boolean matches(HopDirective dir);
+
+ /**
+ * Returns a string representation of this that can be debugged but not parsed.
+ *
+ * @return The debug string.
+ */
+ public String toDebugString();
+}
+
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/HopSpec.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/HopSpec.java
new file mode 100644
index 00000000000..cfe4ab1b6ef
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/HopSpec.java
@@ -0,0 +1,353 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Along with the {@link RoutingSpec}, {@link RoutingTableSpec} and {@link RouteSpec}, this holds the routing
+ * specifications for all protocols. The only way a client can configure or alter the settings of a message bus instance
+ * is through these classes.
+ * <p>
+ * This class contains the spec for a single hop.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class HopSpec {
+
+ private final String name;
+ private final String selector;
+ private final List<String> recipients = new ArrayList<>();
+ private final boolean verify;
+ private boolean ignoreResult = false;
+
+ /**
+ * Creates a new named hop specification.
+ *
+ * @param name A protocol-unique name for this hop.
+ * @param selector A string that represents the selector for this hop.
+ */
+ public HopSpec(String name, String selector) {
+ this(name, selector, true);
+ }
+
+ /**
+ * Creates a new named hop specification.
+ *
+ * @param name A protocol-unique name for this hop.
+ * @param selector A string that represents the selector for this hop.
+ * @param verify Whether or not this should be verified.
+ */
+ public HopSpec(String name, String selector, boolean verify) {
+ this.name = name;
+ this.selector = selector;
+ this.verify = verify;
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param obj The object to copy.
+ */
+ public HopSpec(HopSpec obj) {
+ this.name = obj.name;
+ this.selector = obj.selector;
+ this.verify = obj.verify;
+ for (String recipient : obj.recipients) {
+ recipients.add(recipient);
+ }
+ this.ignoreResult = obj.ignoreResult;
+ }
+
+ /**
+ * Returns the protocol-unique name of this hop.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the string selector that resolves the recipients of this hop.
+ *
+ * @return The selector.
+ */
+ public String getSelector() {
+ return selector;
+ }
+
+ /**
+ * Returns whether or not there are any recipients that the selector can choose from.
+ *
+ * @return True if there is at least one recipient.
+ */
+ public boolean hasRecipients() {
+ return !recipients.isEmpty();
+ }
+
+ /**
+ * Returns the number of recipients that the selector can choose from.
+ *
+ * @return The number of recipients.
+ */
+ public int getNumRecipients() {
+ return recipients.size();
+ }
+
+ /**
+ * Returns the recipients at the given index.
+ *
+ * @param i The index of the recipient to return.
+ * @return The recipient at the given index.
+ */
+ public String getRecipient(int i) {
+ return recipients.get(i);
+ }
+
+ /**
+ * Adds the given recipient to this.
+ *
+ * @param recipient The recipient to add.
+ * @return This, to allow chaining.
+ */
+ public HopSpec addRecipient(String recipient) {
+ recipients.add(recipient);
+ return this;
+ }
+
+ /**
+ * Adds the given recipients to this.
+ *
+ * @param recipients The recipients to add.
+ * @return This, to allow chaining.
+ */
+ public HopSpec addRecipients(List<String> recipients) {
+ this.recipients.addAll(recipients);
+ return this;
+ }
+
+ /**
+ * Sets the recipient at the given index.
+ *
+ * @param i The index at which to set the recipient.
+ * @param recipient The recipient to set.
+ * @return This, to allow chaining.
+ */
+ public HopSpec setRecipient(int i, String recipient) {
+ recipients.set(i, recipient);
+ return this;
+ }
+
+ /**
+ * Removes the recipient at the given index.
+ *
+ * @param i The index of the recipient to remove.
+ * @return The removed recipient.
+ */
+ public String removeRecipient(int i) {
+ return recipients.remove(i);
+ }
+
+ /**
+ * Clears the list of recipients that the selector may choose from.
+ *
+ * @return This, to allow chaining.
+ */
+ public HopSpec clearRecipients() {
+ recipients.clear();
+ return this;
+ }
+
+ /**
+ * Returns whether or not to ignore the result when routing through this hop.
+ *
+ * @return True to ignore the result.
+ */
+ public boolean getIgnoreResult() {
+ return ignoreResult;
+ }
+
+ /**
+ * Sets whether or not to ignore the result when routing through this hop.
+ *
+ * @param ignoreResult Whether or not to ignore the result.
+ * @return This, to allow chaining.
+ */
+ public HopSpec setIgnoreResult(boolean ignoreResult) {
+ this.ignoreResult = ignoreResult;
+ return this;
+ }
+
+ /**
+ * Verifies the content of this against the given application.
+ *
+ * @param app The application to verify against.
+ * @param table The routing table to verify against.
+ * @param errors The list of errors found.
+ * @return True if no errors where found.
+ */
+ public boolean verify(ApplicationSpec app, RoutingTableSpec table, List<String> errors) {
+ if (verify) {
+ verify(app, table, null, recipients, selector, errors,
+ "hop '" + name + "' in routing table '" + table.getProtocol() + "'");
+ }
+ return errors.isEmpty();
+ }
+
+ /**
+ * Verifies that the hop given by the given selector and children is valid.
+ *
+ * @param app The application to verify against.
+ * @param table The routing table to verify against.
+ * @param parent The parent hop that the selector must match.
+ * @param selector The selector to verify.
+ * @param children The children to verify, may be null or empty.
+ * @param errors The list of errors found.
+ * @param context The context to use if adding an error.
+ * @return True if no errors where found.
+ */
+ static boolean verify(ApplicationSpec app, RoutingTableSpec table, Hop parent, List<String> children,
+ String selector, List<String> errors, String context) {
+ // Verify that the selector can be parsed.
+ Hop hop = Hop.parse(selector);
+ for (int i = 0, len = hop.getNumDirectives(); i < len; ++i) {
+ HopDirective dir = hop.getDirective(i);
+ if (dir instanceof ErrorDirective) {
+ errors.add("For " + context + "; " + ((ErrorDirective)dir).getMessage());
+ return false;
+ }
+ }
+
+ // Verify that the parent matches this, if any.
+ if (parent != null) {
+ if (parent.getNumDirectives() == 1) {
+ // hops that contain a single policy directive will typically be magic
+ } else if (!parent.matches(hop)) {
+ errors.add("Selector '" + parent.getServiceName() + "' does not match " + context + ".");
+ return false;
+ }
+ }
+
+ // Traverse and verify the directives of the hop.
+ boolean verifyServiceName = true;
+ boolean allowRecipients = false;
+ for (int i = 0, len = hop.getNumDirectives(); i < len; ++i) {
+ HopDirective dir = hop.getDirective(i);
+ if (dir instanceof ErrorDirective) {
+ // caught above
+ } else if (dir instanceof PolicyDirective) {
+ allowRecipients = true;
+ verifyServiceName = false;
+ } else if (dir instanceof RouteDirective) {
+ String routeName = ((RouteDirective)dir).getName();
+ if (!table.hasRoute(routeName)) {
+ errors.add(capitalize(context) + " references route '" + routeName + "' which does not exist.");
+ return false;
+ }
+ verifyServiceName = false;
+ } else if (dir instanceof TcpDirective) {
+ verifyServiceName = false;
+ } else if (dir instanceof VerbatimDirective) {
+ // will be verified below
+ }
+ }
+
+ // Verify that the service referenced by the hop exists, if required.
+ if (verifyServiceName) {
+ String serviceName = hop.getServiceName();
+ if (table.hasRoute(serviceName)) {
+ // all good
+ } else if (table.hasHop(serviceName)) {
+ // also good
+ } else if (!app.isService(table.getProtocol(), serviceName)) {
+ errors.add(capitalize(context) + " references '" + serviceName + "' which is neither a service," +
+ " a route nor another hop.");
+ return false;
+ }
+ }
+
+ // Verify that recipients are allowed and that they are valid themselves.
+ if (children != null && children.size() > 0) {
+ if (!allowRecipients) {
+ errors.add(capitalize(context) + " has recipients but no policy directive.");
+ return false;
+ }
+ for (String child : children) {
+ verify(app, table, hop, null, child, errors, "recipient '" + child + "' in " + context);
+ }
+ }
+
+ // All ok.
+ return true;
+ }
+
+ /**
+ * Appends the content of this to the given config string builder.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ public void toConfig(StringBuilder cfg, String prefix) {
+ cfg.append(prefix).append("name ").append(RoutingSpec.toConfigString(name)).append("\n");
+ cfg.append(prefix).append("selector ").append(RoutingSpec.toConfigString(selector)).append("\n");
+ if (ignoreResult) {
+ cfg.append(prefix).append("ignoreresult true\n");
+ }
+ int numRecipients = recipients.size();
+ if (numRecipients > 0) {
+ cfg.append(prefix).append("recipient[").append(numRecipients).append("]\n");
+ for (int i = 0; i < numRecipients; ++i) {
+ cfg.append(prefix).append("recipient[").append(i).append("] ");
+ cfg.append(RoutingSpec.toConfigString(recipients.get(i))).append("\n");
+ }
+ }
+ }
+
+ /**
+ * Capitalizes the given string by upper-casing its first letter.
+ *
+ * @param str The string to capitalize.
+ * @return The capitalized string.
+ */
+ private static String capitalize(String str) {
+ return Character.toUpperCase(str.charAt(0)) + str.substring(1);
+ }
+
+ // Overrides Object.
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder();
+ toConfig(ret, "");
+ return ret.toString();
+ }
+
+ // Overrides Object.
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HopSpec)) {
+ return false;
+ }
+ HopSpec rhs = (HopSpec)obj;
+ if (!name.equals(rhs.name)) {
+ return false;
+ }
+ if (!selector.equals(rhs.selector)) {
+ return false;
+ }
+ if (!recipients.equals(rhs.recipients)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (selector != null ? selector.hashCode() : 0);
+ result = 31 * result + (recipients != null ? recipients.hashCode() : 0);
+ result = 31 * result + (verify ? 1 : 0);
+ result = 31 * result + (ignoreResult ? 1 : 0);
+ return result;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/PolicyDirective.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/PolicyDirective.java
new file mode 100755
index 00000000000..e4b88dca6e7
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/PolicyDirective.java
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * This class represents a policy directive within a {@link Hop}'s selector. This means to create the named protocol
+ * using the given parameter string, and the running that protocol within the context of this directive.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class PolicyDirective implements HopDirective {
+
+ private final String name;
+ private final String param;
+
+ /**
+ * Constructs a new policy selector item.
+ *
+ * @param name The name of the policy to invoke.
+ * @param param The parameter to pass to the name constructor.
+ */
+ public PolicyDirective(String name, String param) {
+ this.name = name;
+ this.param = param;
+ }
+
+ @Override
+ public boolean matches(HopDirective dir) {
+ return true;
+ }
+
+ /**
+ * Returns the name of the policy that this item is to invoke.
+ *
+ * @return The name name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the parameter string for this policy directive.
+ *
+ * @return The parameter.
+ */
+ public String getParam() {
+ return param;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PolicyDirective)) {
+ return false;
+ }
+ PolicyDirective rhs = (PolicyDirective)obj;
+ if (!name.equals(rhs.name)) {
+ return false;
+ }
+ if (param == null && rhs.param != null) {
+ return false;
+ }
+ if (param != null && !param.equals(rhs.param)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + name + (param != null && param.length() > 0 ? ":" + param : "") + "]";
+ }
+
+ @Override
+ public String toDebugString() {
+ return "PolicyDirective(name = '" + name + "', param = '" + param + "')";
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (param != null ? param.hashCode() : 0);
+ return result;
+ }
+} \ No newline at end of file
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/Resender.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/Resender.java
new file mode 100755
index 00000000000..7cb42da3219
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/Resender.java
@@ -0,0 +1,143 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.concurrent.SystemTimer;
+import com.yahoo.messagebus.*;
+
+import java.util.PriorityQueue;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * The resender handles scheduling and execution of sending instances of {@link RoutingNode}. An instance of this class
+ * is owned by {@link com.yahoo.messagebus.MessageBus}. Because this class does not have any internal thread, it depends
+ * on message bus to keep polling it whenever it has time.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Resender {
+
+ private final PriorityQueue<Entry> queue = new PriorityQueue<Entry>();
+ private final RetryPolicy retryPolicy;
+
+ /**
+ * Constructs a new resender.
+ *
+ * @param retryPolicy The retry policy to use.
+ */
+ public Resender(RetryPolicy retryPolicy) {
+ this.retryPolicy = retryPolicy;
+ }
+
+ /**
+ * Returns whether or not the current {@link RetryPolicy} supports resending a {@link Reply} that contains an error
+ * with the given error code.
+ *
+ * @param errorCode The code to check.
+ * @return True if the message can be resent.
+ */
+ public boolean canRetry(int errorCode) {
+ return retryPolicy.canRetry(errorCode);
+ }
+
+ /**
+ * Returns whether or not the given reply should be retried.
+ *
+ * @param reply The reply to check.
+ * @return True if retry is required.
+ */
+ public boolean shouldRetry(Reply reply) {
+ int numErrors = reply.getNumErrors();
+ if (numErrors == 0) {
+ return false;
+ }
+ for (int i = 0; i < numErrors; ++i) {
+ if (!retryPolicy.canRetry(reply.getError(i).getCode())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Schedules the given node for resending, if enabled. This will invoke {@link com.yahoo.messagebus.routing.RoutingNode#prepareForRetry()}
+ * if the node was queued. This method is NOT thread-safe, and should only be called by the messenger thread.
+ *
+ * @param node The node to resend.
+ * @return True if the node was queued.
+ */
+ public boolean scheduleRetry(RoutingNode node) {
+ Message msg = node.getMessage();
+ if (!msg.getRetryEnabled()) {
+ return false;
+ }
+ int retry = msg.getRetry() + 1;
+ double delay = node.getReply().getRetryDelay();
+ if (delay < 0) {
+ delay = retryPolicy.getRetryDelay(retry);
+ }
+ if (msg.getTimeRemainingNow() * 0.001 - delay <= 0) {
+ node.addError(ErrorCode.TIMEOUT, "Timeout exceeded by resender, giving up.");
+ return false;
+ }
+ node.prepareForRetry(); // consumes the reply
+ node.getTrace().trace(TraceLevel.COMPONENT,
+ "Message scheduled for retry " + retry + " in " + delay + " seconds.");
+ msg.setRetry(retry);
+ queue.add(new Entry(node, SystemTimer.INSTANCE.milliTime() + (long)(delay * 1000)));
+ return true;
+ }
+
+ /**
+ * Invokes {@link RoutingNode#send()} on all routing nodes that are applicable for sending at the current time.
+ */
+ public void resendScheduled() {
+ if (queue.isEmpty()) return;
+
+ List<RoutingNode> sendList = new LinkedList<RoutingNode>();
+ long now = SystemTimer.INSTANCE.milliTime();
+ while (!queue.isEmpty() && queue.peek().time <= now) {
+ sendList.add(queue.poll().node);
+ }
+
+ for (RoutingNode node : sendList) {
+ node.getTrace().trace(TraceLevel.COMPONENT, "Resender resending message.");
+ node.send();
+ }
+ }
+
+ /**
+ * Discards all the routing nodes currently scheduled for resending.
+ */
+ public void destroy() {
+ while (!queue.isEmpty()) {
+ queue.poll().node.discard();
+ }
+ }
+
+ /**
+ * This class encapsulates a routing node and some arbitrary time. This is required for the resending logic so that
+ * it can properly schedule resending.
+ */
+ private static class Entry implements Comparable<Entry> {
+
+ final RoutingNode node;
+ final Long time;
+
+ /**
+ * The default constructor requires initial values for both members.
+ *
+ * @param node The routing node being scheduled.
+ * @param time The time of this schedule.
+ */
+ public Entry(RoutingNode node, long time) {
+ this.node = node;
+ this.time = time;
+ }
+
+ @Override
+ public int compareTo(Entry rhs) {
+ return time.compareTo(rhs.time);
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RetryPolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RetryPolicy.java
new file mode 100644
index 00000000000..19ea949e741
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RetryPolicy.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * When a {@link com.yahoo.messagebus.Reply} containing errors is returned to a {@link com.yahoo.messagebus.MessageBus},
+ * an object implementing this interface is consulted on whether or not to resend the corresponding {@link
+ * com.yahoo.messagebus.Message}. The policy is passed to the message bus at creation time using the {@link
+ * com.yahoo.messagebus.MessageBusParams#setRetryPolicy(RetryPolicy)} method.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface RetryPolicy {
+
+ /**
+ * Returns whether or not a {@link com.yahoo.messagebus.Reply} containing an {@link com.yahoo.messagebus.Error} with
+ * the given error code can be retried. This method is invoked once for each error in a reply.
+ *
+ * @param errorCode The code to check.
+ * @return True if the message can be resent.
+ */
+ public boolean canRetry(int errorCode);
+
+ /**
+ * Returns the number of seconds to delay resending a message.
+ *
+ * @param retry The retry attempt.
+ * @return The delay in seconds.
+ */
+ public double getRetryDelay(int retry);
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RetryTransientErrorsPolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RetryTransientErrorsPolicy.java
new file mode 100644
index 00000000000..128ca3e819d
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RetryTransientErrorsPolicy.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.messagebus.ErrorCode;
+
+/**
+ * Implements a retry policy that allows resending of any error that is not fatal. It also does progressive back-off,
+ * delaying each attempt by the given time multiplied by the retry attempt.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RetryTransientErrorsPolicy implements RetryPolicy {
+
+ private volatile boolean enabled = true;
+ private volatile double baseDelay = 1;
+
+ /**
+ * Sets whether or not this policy should allow retries or not.
+ *
+ * @param enabled True to allow retries.
+ * @return This, to allow chaining.
+ */
+ public RetryTransientErrorsPolicy setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+ /**
+ * Sets the base delay in seconds to wait between retries. This amount is multiplied by the retry number.
+ *
+ * @param baseDelay The time in seconds.
+ * @return This, to allow chaining.
+ */
+ public RetryTransientErrorsPolicy setBaseDelay(double baseDelay) {
+ this.baseDelay = baseDelay;
+ return this;
+ }
+
+ @Override
+ public boolean canRetry(int errorCode) {
+ return enabled && errorCode < ErrorCode.FATAL_ERROR;
+ }
+
+ @Override
+ public double getRetryDelay(int retry) {
+ return baseDelay * retry;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/Route.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/Route.java
new file mode 100755
index 00000000000..c9684e77a66
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/Route.java
@@ -0,0 +1,206 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>A route is a list of {@link Hop hops} that are resolved from first to last
+ * as a routable moves from source to destination. A route may be changed at any
+ * time be either application logic or an invoked {@link RoutingPolicy}, so no
+ * guarantees on actual path can be given without the full knowledge of all that
+ * logic.</p>
+ *
+ * <p>To construct a route you may either use the factory method {@link
+ * #parse(String)} to produce a route instance from a string representation, or
+ * you may build one programatically through the hop accessors.</p>
+ *
+ * @author <a href="bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Route {
+
+ private final List<Hop> hops = new ArrayList<Hop>();
+ private String cache = null;
+
+ /**
+ * <p>Creates an empty route that contains no hops.</p>
+ */
+ public Route() {
+ // empty
+ }
+
+ /**
+ * <p>The copy constructor ignores integrity, it simply duplicates the list
+ * of hops in the other route. If that route is illegal, then so is
+ * this.</p>
+ *
+ * @param route The route to copy.
+ */
+ public Route(Route route) {
+ this(route.hops);
+ cache = route.cache;
+ }
+
+ private void setRaw(String s) {
+ cache = s;
+ }
+
+ /**
+ * <p>Parses the given string as a list of space-separated hops. The {@link
+ * #toString()} method is compatible with this parser.</p>
+ *
+ * @param str The string to parse.
+ * @return A route that corresponds to the string.
+ */
+ public static Route parse(String str) {
+ if (str == null || str.length() == 0) {
+ return new Route().addHop(new Hop().addDirective(new ErrorDirective("Failed to parse empty string.")));
+ }
+ RouteParser parser = new RouteParser(str);
+ Route route = parser.route();
+ route.setRaw(str);
+ return route;
+ }
+
+ /**
+ * <p>Constructs a route based on a list of hops.</p>
+ *
+ * @param hops The hops to be contained in this.
+ */
+ public Route(List<Hop> hops) {
+ this.hops.addAll(hops);
+ }
+
+ /**
+ * <p>Returns whether or not there are any hops in this route.</p>
+ *
+ * @return True if there is at least one hop.
+ */
+ public boolean hasHops() {
+ return !hops.isEmpty();
+ }
+
+ /**
+ * <p>Returns the number of hops that make up this route.</p>
+ *
+ * @return The number of hops.
+ */
+ public int getNumHops() {
+ return hops.size();
+ }
+
+ /**
+ * <p>Returns the hop at the given index.</p>
+ *
+ * @param i The index of the hop to return.
+ * @return The hop.
+ */
+ public Hop getHop(int i) {
+ return hops.get(i);
+ }
+
+ /**
+ * <p>Adds a hop to the list of hops that make up this route.</p>
+ *
+ * @param hop The hop to add.
+ * @return This, to allow chaining.
+ */
+ public Route addHop(Hop hop) {
+ cache = null;
+ hops.add(hop);
+ return this;
+ }
+
+ /**
+ * <p>Sets the hop at a given index.</p>
+ *
+ * @param i The index at which to set the hop.
+ * @param hop The hop to set.
+ * @return This, to allow chaining.
+ */
+ public Route setHop(int i, Hop hop) {
+ cache = null;
+ hops.set(i, hop);
+ return this;
+ }
+
+ /**
+ * <p>Removes the hop at a given index.</p>
+ *
+ * @param i The index of the hop to remove.
+ * @return The hop removed.
+ */
+ public Hop removeHop(int i) {
+ cache = null;
+ return hops.remove(i);
+ }
+
+ /**
+ * <p>Clears the list of hops that make up this route.</p>
+ *
+ * @return This, to allow chaining.
+ */
+ public Route clearHops() {
+ cache = null;
+ hops.clear();
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Route)) {
+ return false;
+ }
+ Route rhs = (Route)obj;
+ if (hops.size() != rhs.hops.size()) {
+ return false;
+ }
+ for (int i = 0; i < hops.size(); ++i) {
+ if (!hops.get(i).equals(rhs.hops.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ if (cache == null) {
+ StringBuilder ret = new StringBuilder("");
+ for (int i = 0; i < hops.size(); ++i) {
+ ret.append(hops.get(i));
+ if (i < hops.size() - 1) {
+ ret.append(" ");
+ }
+ }
+ cache = ret.toString();
+ }
+ return cache;
+ }
+
+ /**
+ * <p>Returns a string representation of this that can be debugged but not
+ * parsed.</p>
+ *
+ * @return The debug string.
+ */
+ public String toDebugString() {
+ StringBuilder ret = new StringBuilder("Route(hops = { ");
+ for (int i = 0; i < hops.size(); ++i) {
+ ret.append(hops.get(i).toDebugString());
+ if (i < hops.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" })");
+ return ret.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = hops != null ? hops.hashCode() : 0;
+ result = 31 * result + (cache != null ? cache.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteDirective.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteDirective.java
new file mode 100755
index 00000000000..6ea79a256e0
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteDirective.java
@@ -0,0 +1,63 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * This class represents a route directive within a {@link Hop}'s selector. This will be replaced by the named route
+ * when evaluated. If the route is not present in the running protocol's routing table, routing will fail.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RouteDirective implements HopDirective {
+
+ private final String name;
+
+ /**
+ * Constructs a new directive to insert a route.
+ *
+ * @param name The name of the route to insert.
+ */
+ public RouteDirective(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean matches(HopDirective dir) {
+ return dir instanceof RouteDirective && name.equals(((RouteDirective)dir).name);
+ }
+
+ /**
+ * Returns the name of the route to insert.
+ *
+ * @return The route name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RouteDirective)) {
+ return false;
+ }
+ RouteDirective rhs = (RouteDirective)obj;
+ if (!name.equals(rhs.name)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "route:" + name;
+ }
+
+ @Override
+ public String toDebugString() {
+ return "RouteDirective(name = '" + name + "')";
+ }
+
+ @Override
+ public int hashCode() {
+ return name != null ? name.hashCode() : 0;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteParser.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteParser.java
new file mode 100644
index 00000000000..a9f2ed8de3c
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteParser.java
@@ -0,0 +1,118 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * This replaces the incredibly slow javacc RouteParser.jj. It is a has its c++ sibling and
+ * the implementation is a a copy of the C++ version.
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @since 5.2
+ */
+
+public class RouteParser {
+ private final String routeText;
+ RouteParser(String route) {
+ this.routeText = route;
+ }
+
+ Route route() {
+ Route route = new Route();
+ for (int from = 0, at = 0, depth = 0; at <= routeText.length(); ++at) {
+ if (at == routeText.length() || ((depth == 0) && Character.isWhitespace(routeText.charAt(at)))) {
+ if (from < at) {
+ Hop hop = createHop(routeText.substring(from, at));
+ if (hop.hasDirectives() && hop.getDirective(0) instanceof ErrorDirective) {
+ return new Route().addHop(new Hop().addDirective(hop.getDirective(0)));
+ }
+ route.addHop(hop);
+ }
+ from = at + 1;
+ } else if ((routeText.charAt(at) == '(') || (routeText.charAt(at) == '[') ) {
+ ++depth;
+ } else if ((routeText.charAt(at) == ')') || (routeText.charAt(at) == ']')) {
+ --depth;
+ }
+ }
+ return route;
+ }
+ private static Hop createHop(String s) {
+ final int len = s.length();
+ if (len == 0) {
+ return new Hop().addDirective(createErrorDirective("Failed to parse empty string."));
+ } else if (len > 1 && (s.charAt(0) == '?')) {
+ return createHop(s.substring(1, len)).setIgnoreResult(true);
+ } else if (len > 4 && s.charAt(0) == 't' && s.charAt(1) == 'c' && s.charAt(2) == 'p' && s.charAt(3) == '/') {
+ HopDirective directive = createTcpDirective(s.substring(4, len));
+ if (directive != null) {
+ return new Hop().addDirective(directive);
+ }
+ } else if (len > 6 && s.charAt(0) == 'r' && s.charAt(1) == 'o' && s.charAt(2) == 'u'
+ && s.charAt(3) == 't' && s.charAt(4) == 'e' && s.charAt(5) == ':') {
+ return new Hop().addDirective(createRouteDirective(s.substring(6, len)));
+ }
+ Hop hop = new Hop();
+ for (int from = 0, at = 0, depth = 0; at <= len; ++at) {
+ if (at == len) {
+ if (depth > 0) {
+ return new Hop().addDirective(createErrorDirective("Unterminated '[' in '" + s + "'"));
+ }
+ hop.addDirective(createDirective(s.substring(from, at)));
+ from = at + 1;
+ } else {
+ char c = s.charAt(at);
+ if (Character.isWhitespace(c) && depth == 0) {
+ return new Hop().addDirective(createErrorDirective("Failed to completely parse '" + s + "'."));
+ } else if ((depth == 0 && c == '/')) {
+ hop.addDirective(createDirective(s.substring(from, at)));
+ from = at + 1;
+ } else if (c == '[') {
+ ++depth;
+ } else if (c == ']') {
+ if (depth == 0) {
+ return new Hop().addDirective(createErrorDirective("Unexpected token ']' in '" + s + "'"));
+ }
+ --depth;
+ }
+ }
+ }
+ return hop;
+ }
+ private static HopDirective createErrorDirective(String s) {
+ return new ErrorDirective(s);
+ }
+ private static HopDirective createTcpDirective(String s) {
+ int posP = s.indexOf(':');
+ if (posP <= 0) {
+ return null;
+ }
+ int posS = s.indexOf('/', posP+1);
+ if (posS <= posP + 1) {
+ return null;
+ }
+ return new TcpDirective(s.substring(0, posP),
+ Integer.valueOf(s.substring(posP + 1, posS)),
+ s.substring(posS + 1));
+ }
+ private static HopDirective createRouteDirective(String s) {
+ return new RouteDirective(s);
+ }
+ private static HopDirective createDirective(String s) {
+ return (s.length() > 2 && s.charAt(0) == '[')
+ ? ((s.charAt(s.length() - 1) == ']')
+ ? createPolicyDirective(s.substring(1, s.length() - 1))
+ : createErrorDirective("Unterminated '[' in '" + s + "'"))
+ : createVerbatimDirective(s);
+
+ }
+ private static HopDirective createPolicyDirective(String s)
+ {
+ int pos = s.indexOf(':');
+ return (pos == -1)
+ ? new PolicyDirective(s, "")
+ : new PolicyDirective(s.substring(0, pos), s.substring(pos + 1).trim());
+ }
+
+ private static HopDirective createVerbatimDirective(String s) {
+ return new VerbatimDirective(s);
+ }
+
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteSpec.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteSpec.java
new file mode 100644
index 00000000000..4062a0d9d0f
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RouteSpec.java
@@ -0,0 +1,219 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Along with the {@link RoutingSpec}, {@link RoutingTableSpec} and {@link HopSpec}, this holds the routing
+ * specifications for all protocols. The only way a client can configure or alter the settings of a message bus instance
+ * is through these classes.
+ * <p>
+ * This class contains the spec for a single route.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RouteSpec {
+
+ private final String name;
+ private final List<String> hops = new ArrayList<String>();
+ private final boolean verify;
+
+ /**
+ * Creates a new named route specification.
+ *
+ * @param name A protocol-unique name for this route.
+ */
+ public RouteSpec(String name) {
+ this(name, true);
+ }
+
+ /**
+ * Creates a new named route specification.
+ *
+ * @param name A protocol-unique name for this route.
+ * @param verify Whether or not this should be verified.
+ */
+ public RouteSpec(String name, boolean verify) {
+ this.name = name;
+ this.verify = verify;
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param obj The object to copy.
+ */
+ public RouteSpec(RouteSpec obj) {
+ this.name = obj.name;
+ this.verify = obj.verify;
+ for (String hop : obj.hops) {
+ hops.add(hop);
+ }
+ }
+
+ /**
+ * Returns the protocol-unique name of this route.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the hop name at the given index.
+ *
+ * @param i The index of the hop to return.
+ * @return The hop at the given index.
+ */
+ public String getHop(int i) {
+ return hops.get(i);
+ }
+
+ /**
+ * Returns whether or not there are any hops in this route.
+ *
+ * @return True if there is at least one hop.
+ */
+ public boolean hasHops() {
+ return !hops.isEmpty();
+ }
+
+ /**
+ * Returns the number of hops that make up this route.
+ *
+ * @return The number of hops.
+ */
+ public int getNumHops() {
+ return hops.size();
+ }
+
+ /**
+ * Adds the given hop name to this.
+ *
+ * @param hop The hop to add.
+ * @return This, to allow chaining.
+ */
+ public RouteSpec addHop(String hop) {
+ hops.add(hop);
+ return this;
+ }
+
+ /**
+ * Adds the given hop names to this.
+ *
+ * @param hops The hops to add.
+ * @return This, to allow chaining.
+ */
+ public RouteSpec addHops(List<String> hops) {
+ this.hops.addAll(hops);
+ return this;
+ }
+
+ /**
+ * Sets the hop name for a given index.
+ *
+ * @param i The index of the hop to set.
+ * @param hop The hop to set.
+ * @return This, to allow chaining.
+ */
+ public RouteSpec setHop(int i, String hop) {
+ hops.set(i, hop);
+ return this;
+ }
+
+ /**
+ * Removes the hop name at the given index.
+ *
+ * @param i The index of the hop to remove.
+ * @return The removed hop.
+ */
+ public String removeHop(int i) {
+ return hops.remove(i);
+ }
+
+ /**
+ * Clears the list of hops that make up this route.
+ *
+ * @return This, to allow chaining.
+ */
+ public RouteSpec clearHops() {
+ hops.clear();
+ return this;
+ }
+
+ /**
+ * Verifies the content of this against the given application.
+ *
+ * @param app The application to verify against.
+ * @param table The routing table to verify against.
+ * @param errors The list of errors found.
+ * @return True if no errors where found.
+ */
+ public boolean verify(ApplicationSpec app, RoutingTableSpec table, List<String> errors) {
+ if (verify) {
+ String protocol = table.getProtocol();
+ int numHops = hops.size();
+ if (numHops == 0) {
+ errors.add("Route '" + name + "' in routing table '" + protocol + "' has no hops.");
+ } else {
+ for (int i = 0; i < numHops; ++i) {
+ HopSpec.verify(app, table, null, null, hops.get(i), errors,
+ "hop " + (i + 1) + " in route '" + name + "' in routing table '" + protocol + "'");
+ }
+ }
+ }
+ return errors.isEmpty();
+ }
+
+ /**
+ * Appends the content of this to the given config string builder.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ public void toConfig(StringBuilder cfg, String prefix) {
+ cfg.append(prefix).append("name ").append(RoutingSpec.toConfigString(name)).append("\n");
+ int numHops = hops.size();
+ if (numHops > 0) {
+ cfg.append(prefix).append("hop[").append(numHops).append("]\n");
+ for (int i = 0; i < numHops; ++i) {
+ cfg.append(prefix).append("hop[").append(i).append("] ");
+ cfg.append(RoutingSpec.toConfigString(hops.get(i))).append("\n");
+ }
+ }
+ }
+
+ // Overrides Object.
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder();
+ toConfig(ret, "");
+ return ret.toString();
+ }
+
+ // Overrides Object.
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RouteSpec)) {
+ return false;
+ }
+ RouteSpec rhs = (RouteSpec)obj;
+ if (!name.equals(rhs.name)) {
+ return false;
+ }
+ if (!hops.equals(rhs.hops)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (hops != null ? hops.hashCode() : 0);
+ result = 31 * result + (verify ? 1 : 0);
+ return result;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingContext.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingContext.java
new file mode 100755
index 00000000000..d7105a92f16
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingContext.java
@@ -0,0 +1,383 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.jrt.slobrok.api.IMirror;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.*;
+
+import java.util.*;
+
+/**
+ * <p>This context object is what is seen by {@link RoutingPolicy} when doing
+ * both select() and merge(). It contains the necessary accessors to everything
+ * a policy is expected to need. An instance of this is created for every {@link
+ * RoutingNode} that contains a policy.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingContext {
+
+ private final RoutingNode node;
+ private final int directive;
+ private final Set<Integer> consumableErrors = new HashSet<Integer>();
+ private boolean selectOnRetry = true;
+ private Object context = null;
+
+ /**
+ * <p>Constructs a new routing context for a given routing node and hop.</p>
+ *
+ * @param node The owning routing node.
+ * @param directive The index to the policy directive of the hop.
+ */
+ RoutingContext(RoutingNode node, int directive) {
+ this.node = node;
+ this.directive = directive;
+ }
+
+ /**
+ * <p>Returns whether or not this hop has any configured recipients.</p>
+ *
+ * @return True if there is at least one recipient.
+ */
+ public boolean hasRecipients() {
+ return !node.getRecipients().isEmpty();
+ }
+
+ /**
+ * <p>Returns the number of configured recipients for this hop.</p>
+ *
+ * @return The recipient count.
+ */
+ public int getNumRecipients() {
+ return node.getRecipients().size();
+ }
+
+ /**
+ * <p>Returns the configured recipient at the given index.</p>
+ *
+ * @param idx The index of the recipient to return.
+ * @return The reipient at the given index.
+ */
+ public Route getRecipient(int idx) {
+ return node.getRecipients().get(idx);
+ }
+
+ /**
+ * <p>Returns all configured recipients for this hop.</p>
+ *
+ * @return An unmodifiable list of recipients.
+ */
+ public List<Route> getAllRecipients() {
+ return Collections.unmodifiableList(node.getRecipients());
+ }
+
+ /**
+ * <p>Returns a list of all configured recipients whose first hop matches
+ * this.</p>
+ *
+ * @return A modifiable list of recipients.
+ */
+ public List<Route> getMatchedRecipients() {
+ List<Route> ret = new ArrayList<Route>();
+ Set<String> done = new HashSet<String>();
+ Hop hop = getHop();
+ for (Route route : node.getRecipients()) {
+ if (route.hasHops() && hop.matches(route.getHop(0))) {
+ HopDirective dir = route.getHop(0).getDirective(directive);
+ String key = dir.toString();
+ if (!done.contains(key)) {
+ ret.add(new Route(route).setHop(0, new Hop(hop).setDirective(directive, dir)));
+ done.add(key);
+ }
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * <p>Returns whether or not the policy is required to reselect if resending
+ * occurs.</p>
+ *
+ * @return True to invoke {@link RoutingPolicy#select(RoutingContext)} on
+ * resend.
+ */
+ public boolean getSelectOnRetry() {
+ return selectOnRetry;
+ }
+
+ /**
+ * <p>Sets whether or not the policy is required to reselect if resending
+ * occurs.</p>
+ *
+ * @param selectOnRetry The value to set.
+ * @return This, to allow chaining.
+ */
+ public RoutingContext setSelectOnRetry(boolean selectOnRetry) {
+ this.selectOnRetry = selectOnRetry;
+ return this;
+ }
+
+ /**
+ * <p>Returns the route that contains the routing policy that spawned
+ * this.</p>
+ *
+ * @return The route.
+ */
+ public Route getRoute() {
+ return node.getRoute();
+ }
+
+ /**
+ * <p>Returns the hop that contains the routing policy that spawned
+ * this.</p>
+ *
+ * @return The hop.
+ */
+ public Hop getHop() {
+ return node.getRoute().getHop(0);
+ }
+
+ /**
+ * <p>Returns the index of the hop directive that spawned this.</p>
+ *
+ * @return The directive index.
+ */
+ public int getDirectiveIndex() {
+ return directive;
+ }
+
+ /**
+ * <p>Returns the policy directive that spawned this.</p>
+ *
+ * @return The directive object.
+ */
+ public PolicyDirective getDirective() {
+ return (PolicyDirective)getHop().getDirective(directive);
+ }
+
+ /**
+ * <p>Returns the part of the route string that precedes the active policy
+ * directive. This is the same as calling {@link #getHop()}.getPrefix({@link
+ * #getDirectiveIndex()}).</p>
+ *
+ * @return The hop prefix.
+ */
+ public String getHopPrefix() {
+ return getHop().getPrefix(directive);
+ }
+
+ /**
+ * <p>Returns the remainder of the route string immediately following the
+ * active policy directive. This is the same as calling {@link
+ * #getHop()}.getSuffix({@link #getDirectiveIndex()}).</p>
+ *
+ * @return The hop suffix.
+ */
+ public String getHopSuffix() {
+ return getHop().getSuffix(directive);
+ }
+
+ /**
+ * <p>Returns the policy specific context object.</p>
+ *
+ * @return The context.
+ */
+ public Object getContext() {
+ return context;
+ }
+
+ /**
+ * <p>Sets a policy specific context object that will be available at
+ * merge().</p>
+ *
+ * @param context An arbitrary object.
+ * @return This, to allow chaining.
+ */
+ public RoutingContext setContext(Object context) {
+ this.context = context;
+ return this;
+ }
+
+ /**
+ * <p>Returns the message being routed.</p>
+ *
+ * @return The message.
+ */
+ public Message getMessage() {
+ return node.getMessage();
+ }
+
+ /**
+ * <p>Adds a string to the trace of the message being routed.</p>
+ *
+ * @param level The level of the trace note.
+ * @param note The note to add.
+ */
+ public void trace(int level, String note) {
+ node.getTrace().trace(level, note);
+ }
+
+ /**
+ * Indicates if tracing is enabled at this level.
+ * @param level the level
+ * @return true if tracing is enabled at this level
+ */
+ public boolean shouldTrace(int level) {
+ return node.getTrace().shouldTrace(level);
+ }
+
+ /**
+ * <p>Returns whether or not a reply is available.</p>
+ *
+ * @return True if a reply is set.
+ */
+ public boolean hasReply() {
+ return node.hasReply();
+ }
+
+ /**
+ * <p>Returns the reply generated by the associated routing policy.</p>
+ *
+ * @return The reply.
+ */
+ public Reply getReply() {
+ return node.getReply();
+ }
+
+ /**
+ * <p>Sets the reply generated by the associated routing policy.</p>
+ *
+ * @param reply The reply to set.
+ * @return This, to allow chaining.
+ */
+ public RoutingContext setReply(Reply reply) {
+ node.setReply(reply);
+ return this;
+ }
+
+ /**
+ * <p>This is a convenience method to call {@link #setError(Error)}.</p>
+ *
+ * @param code The code of the error to set.
+ * @param msg The message of the error to set.
+ * @return This, to allow chaining.
+ */
+ public RoutingContext setError(int code, String msg) {
+ node.setError(code, msg);
+ return this;
+ }
+
+ /**
+ * <p>This is a convenience method to assign an {@link EmptyReply}
+ * containing a single error to this. This also fiddles with the trace
+ * object so that the error gets written to it.</p>
+ *
+ * @param err The error to set.
+ * @return This, to allow chaining.
+ * @see #setReply(Reply)
+ */
+ public RoutingContext setError(Error err) {
+ node.setError(err);
+ return this;
+ }
+
+ /**
+ * <p>Returns the message bus instance on which this is running.</p>
+ *
+ * @return The message bus.
+ */
+ public MessageBus getMessageBus() {
+ return node.getMessageBus();
+ }
+
+ /**
+ * <p>Returns whether or not the owning routing node has any child
+ * nodes.</p>
+ *
+ * @return True if there is at least one child.
+ */
+ public boolean hasChildren() {
+ return !node.getChildren().isEmpty();
+ }
+
+ /**
+ * <p>Returns the number of children the owning routing node has.</p>
+ *
+ * @return The child count.
+ */
+ public int getNumChildren() {
+ return node.getChildren().size();
+ }
+
+ /**
+ * <p>Returns an iterator for the child routing nodes of the owning
+ * node.</p>
+ *
+ * @return The iterator.
+ */
+ public RoutingNodeIterator getChildIterator() {
+ return new RoutingNodeIterator(node.getChildren());
+ }
+
+ /**
+ * <p>Adds a child routing context to this based on a given route. This is
+ * the typical entry point a policy will use to select recipients during a
+ * {@link RoutingPolicy#select(RoutingContext)} invokation.</p>
+ *
+ * @param route The route to contain in the child context.
+ */
+ public void addChild(Route route) {
+ node.addChild(route);
+ }
+
+ /**
+ * <p>This is a convenience method to more easily add a list of children to
+ * this. It will simply call the {@link #addChild} method for each element
+ * in the list.</p>
+ *
+ * @param routes A list of routes to add as children.
+ */
+ public void addChildren(List<Route> routes) {
+ if (routes != null) {
+ for (Route route : routes) {
+ addChild(route);
+ }
+ }
+ }
+
+ /**
+ * <p>Returns the local mirror of the system's name server.</p>
+ *
+ * @return The mirror api.
+ */
+ public IMirror getMirror() {
+ return node.getNetwork().getMirror();
+ }
+
+ /**
+ * <p>Adds the given error code to the list of codes that the associated
+ * routing policy <u>may</u> consume. This is used to verify whether or not
+ * a resolved routing tree can succeed if sent. Because verification is only
+ * done before sending, the error types that must be added here are only
+ * those that can be generated by message bus itself.</p>
+ *
+ * @param errorCode The code that might be consumed.
+ * @see RoutingNode#hasUnconsumedErrors()
+ * @see com.yahoo.messagebus.ErrorCode
+ */
+ public void addConsumableError(int errorCode) {
+ consumableErrors.add(errorCode);
+ }
+
+ /**
+ * <p>Returns whether or not the given error code <u>may</u> be consumed by
+ * the associated routing policy.</p>
+ *
+ * @param errorCode The code to check.
+ * @return True if the code may be consumed.
+ * @see #addConsumableError(int)
+ */
+ public boolean isConsumableError(int errorCode) {
+ return consumableErrors.contains(errorCode);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java
new file mode 100755
index 00000000000..e394b133bca
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java
@@ -0,0 +1,802 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.metrics.RouteMetricSet;
+import com.yahoo.messagebus.network.Network;
+import com.yahoo.messagebus.network.ServiceAddress;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class represents a node in the routing tree that is created when a route is resolved. There will be one node per
+ * modification of the route. For every {@link RoutingPolicy} there will be an instance of this that has its policy and
+ * {@link RoutingContext} member set. A policy is oblivious to this class, it can only access the context object.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingNode implements ReplyHandler {
+
+ private final MessageBus mbus;
+ private final Network net;
+ private final Resender resender;
+ private final RoutingNode parent;
+ private final List<Route> recipients = new ArrayList<>();
+ private final List<RoutingNode> children = new ArrayList<>();
+ private final ReplyHandler handler;
+ private final Trace trace;
+ private final AtomicInteger pending = new AtomicInteger(0);
+ private final Message msg;
+ private Reply reply = null;
+ private Route route = null;
+ private RoutingPolicy policy = null;
+ private RoutingContext routingContext = null;
+ private ServiceAddress serviceAddress = null;
+ private boolean isActive = true;
+ private boolean shouldRetry = false;
+ private RouteMetricSet routeMetrics;
+
+ /**
+ * Constructs a new instance of this class. This is the root node constructor, and will be used by the different
+ * sessions for sending messages. Note that the {@link #discard()} functionality of this class is implemented so
+ * that it passes a null reply to the handler to notify the discard.
+ *
+ * @param mbus The message bus on which we are running.
+ * @param net The network layer we are to transmit through.
+ * @param resender The resender to schedule with.
+ * @param handler The handler to receive the final reply.
+ * @param msg The message being sent.
+ */
+ public RoutingNode(MessageBus mbus, Network net, Resender resender, ReplyHandler handler, Message msg) {
+ this.mbus = mbus;
+ this.net = net;
+ this.resender = resender;
+ this.handler = handler;
+ this.msg = msg;
+ this.trace = new Trace(msg.getTrace().getLevel());
+ this.route = msg.getRoute();
+ this.parent = null;
+
+ if (route != null) {
+ routeMetrics = mbus.getMetrics().getRouteMetrics(route);
+ }
+ }
+
+ /**
+ * Constructs a new instance of this class. This is the child node constructor, and is the constructor used when
+ * building the routing tree.
+ *
+ * @param parent The parent routing node.
+ * @param route The route to assign to this.
+ */
+ private RoutingNode(RoutingNode parent, Route route) {
+ mbus = parent.mbus;
+ net = parent.net;
+ resender = parent.resender;
+ handler = null;
+ msg = parent.msg;
+ trace = new Trace(parent.trace.getLevel());
+ this.route = new Route(route);
+ this.parent = parent;
+ recipients.addAll(parent.recipients);
+ }
+
+ /**
+ * Discards this routing node. Invoking this will notify the parent {@link SendProxy} to ensure that the
+ * corresponding message is discarded. This is a required step to ensure safe shutdown if you need to destroy a
+ * message bus instance while there are still routing nodes alive in your application.
+ */
+ public void discard() {
+ if (handler != null) {
+ handler.handleReply(null);
+ } else if (parent != null) {
+ parent.discard();
+ }
+ }
+
+ /**
+ * This is the single entry-point for sending a message along a route. This can only be invoked on the root node of
+ * a routing tree. It runs all the necessary selection, verification and transmission logic. Once this has been
+ * called, it guarantees that a reply is returned to the registered reply handler.
+ */
+ public void send() {
+ if (!resolve(0)) {
+ notifyAbort("Route resolution failed.");
+ } else if (hasUnconsumedErrors()) {
+ notifyAbort("Errors found while resolving route.");
+ } else {
+ notifyTransmit();
+ }
+ }
+
+ /**
+ * This method assigns an error reply to all unsent leaf nodes, and invokes {@link #notifyParent()} on them. This
+ * has the effect of ensuring that a reply will return to sender.
+ *
+ * @param msg The error message to assign.
+ */
+ public void notifyAbort(String msg) {
+ Stack<RoutingNode> stack = new Stack<>();
+ stack.push(this);
+ while (!stack.isEmpty()) {
+ RoutingNode node = stack.pop();
+ if (!node.isActive) {
+ // reply not pending
+ } else if (node.reply != null) {
+ node.notifyParent();
+ } else if (node.children.isEmpty()) {
+ node.setError(ErrorCode.SEND_ABORTED, msg);
+ node.notifyParent();
+ } else {
+ for (RoutingNode child : node.children) {
+ stack.push(child);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method collects all unsent leaf nodes and passes them to {@link Network#send(com.yahoo.messagebus.Message,
+ * java.util.List)}. This is orthogonal to {@link #notifyAbort(String)} in that it ensures that a reply will return
+ * to sender.
+ */
+ private void notifyTransmit() {
+ List<RoutingNode> sendTo = new ArrayList<>();
+ Deque<RoutingNode> stack = new ArrayDeque<>();
+ stack.push(this);
+ while (!stack.isEmpty()) {
+ RoutingNode node = stack.pop();
+ if (node.isActive) {
+ if (node.children.isEmpty()) {
+ if (node.reply != null) {
+ node.notifyParent();
+ } else {
+ sendTo.add(node);
+ }
+ } else {
+ for (RoutingNode child : node.children) {
+ stack.push(child);
+ }
+ }
+ }
+ }
+ if (!sendTo.isEmpty()) {
+ net.send(msg, sendTo);
+ }
+ }
+
+ /**
+ * This method may only be invoked on a root node, as it passes the current reply to the member {@link
+ * ReplyHandler}.
+ */
+ private void notifySender() {
+ reply.getTrace().swap(trace);
+ handler.handleReply(reply);
+ reply = null;
+ }
+
+ /**
+ * This method mergs this node as ready for merge. If it has a parent routing node, its pending member is
+ * decremented. If this causes the parent's pending count to reach zero, its {@link #notifyMerge()} method is
+ * invoked. A special flag is used to make sure that failed resending avoids notifying parents of previously
+ * resolved branches of the tree.
+ */
+ private void notifyParent() {
+ if (serviceAddress != null) {
+ net.freeServiceAddress(this);
+ }
+ tryIgnoreResult();
+ if (parent != null) {
+ parent.notifyMerge();
+ return;
+ }
+ if (shouldRetry && resender.scheduleRetry(this)) {
+ return;
+ }
+ notifySender();
+ }
+
+ /**
+ * This method merges the content of all its children, and invokes itself on the parent node. If not all children
+ * are ready for merg, this method does nothing. The rationale for this is that the last child to receive a reply
+ * will propagate the merge upwards. Once this method reaches the root node, the reply is either scheduled for
+ * resending or passed to the owning reply handler.
+ */
+ private void notifyMerge() {
+ if (pending.decrementAndGet() != 0) {
+ return; // not done yet
+ }
+
+ // Merges the trace information from all children into this. This method takes care not to spend cycles
+ // manipulating the trace in case tracing is disabled.
+ if (trace.getLevel() > 0) {
+ TraceNode tail = new TraceNode();
+ for (RoutingNode child : children) {
+ TraceNode root = child.trace.getRoot();
+ tail.addChild(root);
+ root.clear();
+ }
+ tail.setStrict(false);
+ trace.getRoot().addChild(tail);
+ }
+
+ // Execute the {@link RoutingPolicy#notifyMerge(RoutingContext)} method of the current routing policy. If a
+ // policy fails to produce a reply, this attaches an error reply to this node.
+ PolicyDirective dir = routingContext.getDirective();
+ if (trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ trace.trace(TraceLevel.SPLIT_MERGE, "Routing policy '" + dir.getName() + "' merging replies.");
+ }
+ try {
+ policy.merge(routingContext);
+ } catch (RuntimeException e) {
+ setError(ErrorCode.POLICY_ERROR,
+ "Policy '" + dir.getName() + "' threw an exception; " + e.toString());
+ }
+ if (reply == null) {
+ setError(ErrorCode.APP_FATAL_ERROR,
+ "Routing policy '" + routingContext.getDirective().getName() + "' failed to merge replies.");
+ }
+
+ // Notifies the parent node.
+ notifyParent();
+ }
+
+ /**
+ * This is a helper method to call {@link Hop#getIgnoreResult()} on the first Hop of the current Route.
+ *
+ * @return True to ignore the result.
+ */
+ private boolean shouldIgnoreResult() {
+ return route != null && route.getNumHops() > 0 && route.getHop(0).getIgnoreResult();
+ }
+
+ /**
+ * If a reply has been set containing an error, and {@link #shouldIgnoreResult()} returns <tt>true</tt>, this method
+ * replaces that reply with one that has no error.
+ *
+ * @return Whether or not the reply was replaced.
+ */
+ private boolean tryIgnoreResult() {
+ if (!shouldIgnoreResult()) {
+ return false;
+ }
+ if (reply == null || !reply.hasErrors()) {
+ return false;
+ }
+ setReply(new EmptyReply());
+ trace.trace(TraceLevel.SPLIT_MERGE, "Ignoring errors in reply.");
+ return true;
+ }
+
+ /**
+ * This method is used to reset the internal state of routing nodes that will be resent. If a routing policy sets
+ * {@link RoutingContext#setSelectOnRetry(boolean)} to true, this method will reroute everything from that node
+ * onwards. If that flag is not set, scheduling recurses into any child that got a reply with only transient errors.
+ * Finally, if neither this node or none of its children were scheduled for resending, force reroute from this.
+ */
+ void prepareForRetry() {
+ shouldRetry = false;
+ reply = null;
+ if (routingContext != null && routingContext.getSelectOnRetry()) {
+ children.clear();
+ } else if (!children.isEmpty()) {
+ boolean retryingSome = false;
+ for (RoutingNode child : children) {
+ if (child.shouldRetry || child.reply == null) {
+ child.prepareForRetry();
+ retryingSome = true;
+ }
+ }
+ if (!retryingSome) {
+ // Entering here means we have no children that should be resent even though this node reports a transient
+ // error. The only thing we can do is to reselect from this.
+ children.clear();
+ }
+ }
+ }
+
+ /**
+ * Returns whether or not transmitting along this routing tree can possibly succeed. This evaluates to false if
+ * either a) there are no leaf nodes to send to, or b) some leaf node contains a fatal error that is not masked by a
+ * routing policy above it in the tree. If only transient errors would reach this, the resend flag is set to true.
+ *
+ * @return True if no error reaches this.
+ */
+ private boolean hasUnconsumedErrors() {
+ boolean hasError = false;
+
+ Deque<RoutingNode> stack = new ArrayDeque<>();
+ stack.push(this);
+ while (!stack.isEmpty()) {
+ RoutingNode node = stack.pop();
+ if (node.reply != null) {
+ for (int i = 0; i < node.reply.getNumErrors(); ++i) {
+ int errorCode = node.reply.getError(i).getCode();
+ RoutingNode it = node;
+ while (it != null) {
+ if (it.routingContext != null && it.routingContext.isConsumableError(errorCode)) {
+ errorCode = ErrorCode.NONE;
+ break;
+ }
+ it = it.parent;
+ }
+ if (errorCode != ErrorCode.NONE) {
+ shouldRetry = resender != null && resender.canRetry(errorCode);
+ if (!shouldRetry) {
+ return true; // no need to continue
+ }
+ hasError = true;
+ }
+ }
+ } else {
+ for (RoutingNode child : node.children) {
+ stack.push(child);
+ }
+ }
+ }
+
+ return hasError;
+ }
+
+ /**
+ * This method performs the necessary selection logic to resolve the next step of the current route. There is a hard
+ * limit to how deep the routing tree may resolve to, and if that depth is ever exceeded, this method returns false.
+ * This should only really happen if routing has been misconfigured.
+ *
+ * @param depth The current depth.
+ * @return False if selection failed.
+ */
+ private boolean resolve(int depth) {
+ if (route == null || !route.hasHops()) {
+ setError(ErrorCode.ILLEGAL_ROUTE, "Route has no hops.");
+ return false;
+ }
+ if (!children.isEmpty()) {
+ return resolveChildren(depth + 1);
+ }
+ while (lookupHop() || lookupRoute()) {
+ if (++depth > 64) {
+ break;
+ }
+ }
+ if (depth > 64) {
+ setError(ErrorCode.ILLEGAL_ROUTE, "Depth limit exceeded.");
+ return false;
+ }
+ if (findErrorDirective()) {
+ return false;
+ }
+ if (findPolicyDirective()) {
+ if (executePolicySelect()) {
+ return resolveChildren(depth + 1);
+ }
+ return reply != null;
+ }
+ net.allocServiceAddress(this);
+ return serviceAddress != null || reply != null;
+ }
+
+ /**
+ * This method checks to see whether the string representation of the current hop is actually the name of another.
+ * If a hop is found, the first hop of the current route is replaced by this.
+ *
+ * @return True if a hop was found and added.
+ */
+ private boolean lookupHop() {
+ RoutingTable table = mbus.getRoutingTable(msg.getProtocol());
+ if (table != null) {
+ String name = route.getHop(0).getServiceName();
+ if (table.hasHop(name)) {
+ HopBlueprint hop = table.getHop(name);
+ configureFromBlueprint(hop);
+ if (trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ trace.trace(TraceLevel.SPLIT_MERGE, "Recognized '" + name + "' as " + hop + ".");
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This method checks to see whether the current hop contains a {@link RouteDirective}, or if its string
+ * representation is actually the name of a configured route. If a route is found, the first hop of the current
+ * route is replaced by expanding the named route. If a route directive requests a non-existant route, this method
+ * creates an error-reply for this node.
+ *
+ * @return True if a route was found and added.
+ * @see #insertRoute(Route)
+ */
+ private boolean lookupRoute() {
+ RoutingTable table = mbus.getRoutingTable(msg.getProtocol());
+ Hop hop = route.getHop(0);
+ if (hop.getDirective(0) instanceof RouteDirective) {
+ RouteDirective dir = (RouteDirective)hop.getDirective(0);
+ if (table == null || !table.hasRoute(dir.getName())) {
+ setError(ErrorCode.ILLEGAL_ROUTE, "Route '" + dir.getName() + "' does not exist.");
+ return false;
+ }
+ insertRoute(table.getRoute(dir.getName()));
+ if (trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ trace.trace(TraceLevel.SPLIT_MERGE,
+ "Route '" + dir.getName() + "' retrieved by directive; new route is '" + route + "'.");
+ }
+ return true;
+ }
+ if (table != null) {
+ String name = hop.getServiceName();
+ if (table.hasRoute(name)) {
+ insertRoute(table.getRoute(name));
+ if (trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ trace.trace(TraceLevel.SPLIT_MERGE, "Recognized '" + name + "' as route '" + route + "'.");
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This method replaces the first hop of the current route with the given route.
+ *
+ * @param ins The route to insert.
+ */
+ private void insertRoute(Route ins) {
+ Route route = new Route(ins);
+ if (shouldIgnoreResult()) {
+ route.getHop(0).setIgnoreResult(true);
+ }
+ for (int i = 1; i < this.route.getNumHops(); ++i) {
+ route.addHop(this.route.getHop(i));
+ }
+ this.route = route;
+ }
+
+ /**
+ * This method traverses the current hop looking for an isntance of {@link ErrorDirective}. If one is found, this
+ * method assigns a corresponding error reply to this node.
+ *
+ * @return True if an error was found.
+ */
+ private boolean findErrorDirective() {
+ Hop hop = route.getHop(0);
+ for (int i = 0; i < hop.getNumDirectives(); ++i) {
+ HopDirective dir = hop.getDirective(i);
+ if (dir instanceof ErrorDirective) {
+ setError(ErrorCode.ILLEGAL_ROUTE, ((ErrorDirective)dir).getMessage());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This method traverses the current hop looking for an instance of {@link PolicyDirective}. If one is found, this
+ * method creates and assigns a routing context to this.
+ *
+ * @return True if a policy was found.
+ */
+ private boolean findPolicyDirective() {
+ Hop hop = route.getHop(0);
+ for (int i = 0; i < hop.getNumDirectives(); ++i) {
+ HopDirective dir = hop.getDirective(i);
+ if (dir instanceof PolicyDirective) {
+ routingContext = new RoutingContext(this, i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String exceptionMessageWithTrace(Exception e) {
+ StringWriter sw = new StringWriter();
+ try (PrintWriter pw = new PrintWriter(sw)) {
+ e.printStackTrace(pw);
+ pw.flush();
+ }
+ return sw.toString();
+ }
+
+ /**
+ * Creates the {@link RoutingPolicy} referenced by the current routing context, and executes its {@link
+ * RoutingPolicy#select(RoutingContext)} method.
+ *
+ * @return True if at least one child was added.
+ */
+ private boolean executePolicySelect() {
+ PolicyDirective dir = routingContext.getDirective();
+ policy = mbus.getRoutingPolicy(msg.getProtocol(), dir.getName(), dir.getParam());
+ if (policy == null) {
+ setError(ErrorCode.UNKNOWN_POLICY,
+ "Protocol '" + msg.getProtocol() + "' could not create routing policy '" +
+ dir.getName() + "' with parameter '" + dir.getParam() + "'.");
+ return false;
+ }
+ if (trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ trace.trace(TraceLevel.SPLIT_MERGE, "Running routing policy '" + dir.getName() + "'.");
+ }
+ try {
+ policy.select(routingContext);
+ } catch (RuntimeException e) {
+ setError(ErrorCode.POLICY_ERROR,
+ "Policy '" + dir.getName() + "' threw an exception; " + exceptionMessageWithTrace(e));
+ return false;
+ }
+ if (children.isEmpty()) {
+ if (reply == null) {
+ setError(ErrorCode.NO_SERVICES_FOR_ROUTE,
+ "Policy '" + dir.getName() + "' selected no recipients for route '" + route + "'.");
+ } else {
+ if (trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ trace.trace(TraceLevel.SPLIT_MERGE,
+ "Policy '" + dir.getName() + "' assigned a reply to this branch.");
+ }
+ }
+ return false;
+ }
+ for (RoutingNode child : children) {
+ if (child.trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ Hop hop = child.route.getHop(0);
+ child.trace.trace(TraceLevel.SPLIT_MERGE,
+ "Component '" + hop + "' selected by policy '" + dir.getName() + "'.");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This method invokes the {@link #resolve(int)} method of all the child nodes of this. If any of these exceed the
+ * depth limit, this method returns false.
+ *
+ * @param childDepth The depth of the children.
+ * @return False if depth limit was exceeded.
+ */
+ private boolean resolveChildren(int childDepth) {
+ int numActiveChildren = 0;
+ boolean ret = true;
+ for (RoutingNode child : children) {
+ if (child.trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ child.trace.trace(TraceLevel.SPLIT_MERGE, "Resolving '" + child.route + "'.");
+ }
+ child.isActive = (child.reply == null);
+ if (child.isActive) {
+ ++numActiveChildren;
+ if (!child.resolve(childDepth)) {
+ ret = false;
+ break;
+ }
+ } else {
+ if (child.trace.shouldTrace(TraceLevel.SPLIT_MERGE)) {
+ child.trace.trace(TraceLevel.SPLIT_MERGE, "Already completed.");
+ }
+ }
+ }
+ pending.set(numActiveChildren);
+ return ret;
+ }
+
+ /**
+ * Adds a child routing node to this based on a route. This is package private because client code should only
+ * access it through a {@link RoutingPolicy} and its {@link RoutingContext#addChild(Route)} method.
+ *
+ * @param route The route to store in the child node.
+ */
+ void addChild(Route route) {
+ RoutingNode child = new RoutingNode(this, route);
+ if (shouldIgnoreResult()) {
+ child.route.getHop(0).setIgnoreResult(true);
+ }
+ children.add(child);
+ }
+
+ /**
+ * Configures this node based on a hop blueprint. For each recipient in the blueprint it creates a copy of the
+ * current route, and sets the first hop of that route to be the configured recipient hop. In effect, this replaces
+ * the current hop and retains the rest of the route.
+ *
+ * @param hop The blueprint to use for configuration.
+ */
+ private void configureFromBlueprint(HopBlueprint hop) {
+ boolean ignoreResult = shouldIgnoreResult();
+ route.setHop(0, hop.create());
+ if (ignoreResult) {
+ route.getHop(0).setIgnoreResult(true);
+ }
+ recipients.clear();
+ for (int r = 0; r < hop.getNumRecipients(); ++r) {
+ Route recipient = new Route();
+ recipient.addHop(hop.getRecipient(r));
+ for (int h = 1; h < route.getNumHops(); ++h) {
+ recipient.addHop(route.getHop(h));
+ }
+ recipients.add(recipient);
+ }
+ }
+
+ /**
+ * This is a convenience method to call {@link #setError(Error)}.
+ *
+ * @param code The code of the error to set.
+ * @param msg The message of the error to set.
+ */
+ public void setError(int code, String msg) {
+ setError(new Error(code, msg));
+ }
+
+ /**
+ * This is a convenience method to assign an {@link EmptyReply} containing a single error to this. This also fiddles
+ * with the trace object so that the error gets written to it.
+ *
+ * @param err The error to set.
+ * @see #setReply(Reply)
+ */
+ public void setError(Error err) {
+ Reply reply = new EmptyReply();
+ reply.getTrace().setLevel(trace.getLevel());
+ reply.addError(err);
+ setReply(reply);
+ }
+
+ /**
+ * This is a convenience method to call {@link #addError(Error)}.
+ *
+ * @param code The code of the error to add.
+ * @param msg The message of the error to add.
+ */
+ public void addError(int code, String msg) {
+ addError(new Error(code, msg));
+ }
+
+ /**
+ * This is a convenience method to add an error to this. If a reply has already been set, this method will add the
+ * error to it. If no reply is set, this method calls {@link #setError(Error)}. This method also fiddles with the
+ * trace object so that the error gets written to it.
+ *
+ * @param err The error to add.
+ */
+ public void addError(Error err) {
+ if (reply != null) {
+ reply.getTrace().swap(trace);
+ reply.addError(err);
+ reply.getTrace().swap(trace);
+ } else {
+ setError(err);
+ }
+ }
+
+ /**
+ * Returns the message bus being used to send the message.
+ *
+ * @return The message bus.
+ */
+ MessageBus getMessageBus() {
+ return mbus;
+ }
+
+ /**
+ * Returns the network being used to send the message.
+ *
+ * @return The network layer.
+ */
+ Network getNetwork() {
+ return net;
+ }
+
+ /**
+ * Returns the message being routed. You should NEVER modify a message that is retrieved from a routing node or
+ * context, as the result of doing so is undefined.
+ *
+ * @return The message being routed.
+ */
+ public Message getMessage() {
+ return msg;
+ }
+
+ /**
+ * Returns the trace object for this node. Each node has a separate trace object so that merging can be done
+ * correctly.
+ *
+ * @return The trace object.
+ */
+ public Trace getTrace() {
+ return trace;
+ }
+
+ /**
+ * Returns the route object as it exists at this point of the tree.
+ *
+ * @return The route at this point.
+ */
+ public Route getRoute() {
+ return route;
+ }
+
+ /**
+ * Returns whether or not this node contains a reply.
+ *
+ * @return True if this node has a reply.
+ */
+ boolean hasReply() {
+ return reply != null;
+ }
+
+ /**
+ * Returns the reply of this node.
+ *
+ * @return The reply assigned to this node.
+ */
+ Reply getReply() {
+ return reply;
+ }
+
+ /**
+ * Sets the reply of this routing node. This method also updates the internal state of this node; it is tagged for
+ * resending if the reply has only transient errors, and the reply's {@link Trace} is copied. This method <u>does
+ * not</u> call the parent node's {@link #notifyMerge()}.
+ *
+ * @param reply The reply to set.
+ */
+ public void setReply(Reply reply) {
+ if (reply != null) {
+ shouldRetry = resender != null && resender.shouldRetry(reply);
+ trace.getRoot().addChild(reply.getTrace().getRoot());
+ reply.getTrace().clear();
+ }
+ this.reply = reply;
+ }
+
+ /**
+ * Returns the list of configured recipient {@link Route routes}. This is accessed by client code through a more
+ * strict api in {@link RoutingContext}.
+ *
+ * @return The list of recipients.
+ */
+ List<Route> getRecipients() {
+ return recipients;
+ }
+
+ /**
+ * Returns the list of current child nodes. This is accessed by client code through a more strict api in {@link
+ * RoutingContext}.
+ *
+ * @return The list of children.
+ */
+ List<RoutingNode> getChildren() {
+ return children;
+ }
+
+ /**
+ * Returns the service address of this node. This is attached by the network layer, and should only ever be present
+ * in leaf nodes.
+ *
+ * @return The recipient address.
+ */
+ public ServiceAddress getServiceAddress() {
+ return serviceAddress;
+ }
+
+ /**
+ * Sets the service address of this node. This is called by the network layer as this calls its {@link
+ * Network#allocServiceAddress(RoutingNode)} method.
+ *
+ * @param serviceAddress The recipient address.
+ */
+ public void setServiceAddress(ServiceAddress serviceAddress) {
+ this.serviceAddress = serviceAddress;
+ }
+
+ @Override
+ public void handleReply(Reply reply) {
+ setReply(reply);
+ if (routeMetrics != null) {
+ for (int i = 0; i < reply.getNumErrors(); i++) {
+ routeMetrics.addError(reply.getError(i));
+ }
+ }
+ notifyParent();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNodeIterator.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNodeIterator.java
new file mode 100755
index 00000000000..dbf152fdfcf
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNodeIterator.java
@@ -0,0 +1,107 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.messagebus.Reply;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implements an iterator for routing nodes. Use {@link RoutingContext#getChildIterator()} to retrieve an instance of
+ * this.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingNodeIterator {
+
+ // The underlying iterator.
+ private Iterator<RoutingNode> it;
+
+ // The current child entry.
+ private RoutingNode entry;
+
+ /**
+ * Constructs a new iterator based on a given list.
+ *
+ * @param children The list to iterate through.
+ */
+ public RoutingNodeIterator(List<RoutingNode> children) {
+ it = children.iterator();
+ next();
+ }
+
+ /**
+ * Steps to the next child in the map.
+ *
+ * @return This, to allow chaining.
+ */
+ public RoutingNodeIterator next() {
+ entry = it.hasNext() ? it.next() : null;
+ return this;
+ }
+
+ /**
+ * Skips the given number of children.
+ *
+ * @param num The number of children to skip.
+ * @return This, to allow chaining.
+ */
+ public RoutingNodeIterator skip(int num) {
+ for (int i = 0; i < num && isValid(); ++i) {
+ next();
+ }
+ return this;
+ }
+
+ /**
+ * Returns whether or not this iterator is valid.
+ *
+ * @return True if we are still pointing to a valid entry.
+ */
+ public boolean isValid() {
+ return entry != null;
+ }
+
+ /**
+ * Returns the route of the current child.
+ *
+ * @return The route.
+ */
+ public Route getRoute() {
+ return entry.getRoute();
+ }
+
+ /**
+ * Returns whether or not a reply is set in the current child.
+ *
+ * @return True if a reply is available.
+ */
+ public boolean hasReply() {
+ return entry.hasReply();
+ }
+
+ /**
+ * Removes and returns the reply of the current child. This is the correct way of reusing a reply of a child node,
+ * the {@link #getReplyRef()} should be used when just inspecting a child reply.
+ *
+ * @return The reply.
+ */
+ public Reply removeReply() {
+ Reply ret = entry.getReply();
+ ret.getTrace().setLevel(entry.getTrace().getLevel());
+ ret.getTrace().swap(entry.getTrace());
+ entry.setReply(null);
+ return ret;
+ }
+
+ /**
+ * Returns the reply of the current child. It is VERY important that the reply returned by this function is not
+ * reused anywhere. This is a reference to another node's reply, do NOT use it for anything but inspection. If you
+ * want to retrieve and reuse it, call {@link #removeReply()} instead.
+ *
+ * @return The reply.
+ */
+ public Reply getReplyRef() {
+ return entry.getReply();
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingPolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingPolicy.java
new file mode 100644
index 00000000000..b8fa37b0542
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingPolicy.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * Decides how to choose between candidate recipients of a hop template point.
+ * <p>
+ * The routing policy will be given an address and a set of recipients matching this address. It is responsible for
+ * choosing one of the given recipients each time {@link #select} is called. The routing policy chooses which recipient
+ * to return each time at its sole discretion, using information in the RoutingContext argument to choose or not as required
+ * to implement the policy.
+ * <p>
+ * Example:
+ * <ul>
+ * <li>The given <i>address</i> is <code>a/b/?</code>
+ * <li>The given <i>recipients</i> are <code>a/b/c, a/b/d and a/b/e</code> - one of these three must be returned on every call to <code>choose</code>
+ * </ul>
+ * <p>
+ * This class is pluggable per template point in the address of a hop.
+ *
+ * @author <a href="bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @author <a href="bratseth@yahoo-inc.com">Simon Thoresen</a>
+ */
+public interface RoutingPolicy {
+
+ /**
+ * This function must choose a set of services that is to receive the given message from a list of possible
+ * recipients. This is done by adding child routing contexts to the argument object. These children can then be
+ * iterated and manipulated even before selection pass is concluded.
+ *
+ * @param context the complete context for the invocation of this policy. Contains all available data.
+ */
+ public void select(RoutingContext context);
+
+ /**
+ * This function is called when all replies have arrived for some message. The implementation is responsible for
+ * merging multiple replies into a single sensible reply. The replies is contained in the child context objects of
+ * the argument context, and then response must be set in that context.
+ *
+ * @param context the complete context for the invocation of this policy. Contains all available data.
+ */
+ public void merge(RoutingContext context);
+
+ /**
+ * Destroys this factory and frees up any resources it has held. Making further calls on a destroyed
+ * factory causes a runtime exception.
+ */
+ public void destroy();
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingSpec.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingSpec.java
new file mode 100644
index 00000000000..c9469e8e368
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingSpec.java
@@ -0,0 +1,225 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Along with the {@link RoutingTableSpec}, {@link RouteSpec} and {@link HopSpec}, this holds the routing specifications
+ * for all protocols. The only way a client can configure or alter the settings of a message bus instance is through
+ * these classes.
+ * <p>
+ * This class is the root spec class for configuring message bus routing.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingSpec {
+
+ private final List<RoutingTableSpec> tables = new ArrayList<>();
+ private final boolean verify;
+
+ /**
+ * Creates an empty specification.
+ */
+ public RoutingSpec() {
+ this(true);
+ }
+
+ /**
+ * Creates an empty specification.
+ *
+ * @param verify Whether or not this should be verified.
+ */
+ public RoutingSpec(boolean verify) {
+ this.verify = verify;
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param spec the spec to copy.
+ */
+ public RoutingSpec(RoutingSpec spec) {
+ verify = spec.verify;
+ for (RoutingTableSpec table : spec.tables) {
+ tables.add(new RoutingTableSpec(table));
+ }
+ }
+
+ /**
+ * Returns whether or not there are routing table specs contained in this.
+ *
+ * @return True if there is at least one table.
+ */
+ public boolean hasTables() {
+ return !tables.isEmpty();
+ }
+
+ /**
+ * Returns the number of routing table specs that are contained in this.
+ *
+ * @return The number of routing tables.
+ */
+ public int getNumTables() {
+ return tables.size();
+ }
+
+ /**
+ * Returns the routing table spec at the given index.
+ *
+ * @param i The index of the routing table to return.
+ * @return The routing table at the given index.
+ */
+ public RoutingTableSpec getTable(int i) {
+ return tables.get(i);
+ }
+
+ /**
+ * Sets the routing table spec at the given index.
+ *
+ * @param i The index at which to set the routing table.
+ * @param table The routing table to set.
+ * @return This, to allow chaining.
+ */
+ public RoutingSpec setTable(int i, RoutingTableSpec table) {
+ tables.set(i, table);
+ return this;
+ }
+
+ /**
+ * Adds a routing table spec to the list of tables.
+ *
+ * @param table The routing table to add.
+ * @return This, to allow chaining.
+ */
+ public RoutingSpec addTable(RoutingTableSpec table) {
+ tables.add(table);
+ return this;
+ }
+
+ /**
+ * Returns the routing table spec at the given index.
+ *
+ * @param i The index of the routing table to remove.
+ * @return The removed routing table.
+ */
+ public RoutingTableSpec removeTable(int i) {
+ return tables.remove(i);
+ }
+
+ /**
+ * Clears the list of routing table specs contained in this.
+ *
+ * @return This, to allow chaining.
+ */
+ public RoutingSpec clearTables() {
+ tables.clear();
+ return this;
+ }
+
+ /**
+ * Verifies the content of this against the given application.
+ *
+ * @param app The application to verify against.
+ * @param errors The list of errors found.
+ * @return True if no errors where found.
+ */
+ public boolean verify(ApplicationSpec app, List<String> errors) {
+ if (verify) {
+ Map<String, Integer> tableNames = new HashMap<>();
+ for (RoutingTableSpec table : tables) {
+ String name = table.getProtocol();
+
+ int count = tableNames.containsKey(name) ? tableNames.get(name) : 0;
+ tableNames.put(name, count + 1);
+ table.verify(app, errors);
+ }
+ for (Map.Entry<String, Integer> entry : tableNames.entrySet()) {
+ int count = entry.getValue();
+ if (count > 1) {
+ errors.add("Routing table '" + entry.getKey() + "' is defined " + count + " times.");
+ }
+ }
+ }
+ return errors.isEmpty();
+ }
+
+ /**
+ * Appends the content of this to the given config string builder.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ public void toConfig(StringBuilder cfg, String prefix) {
+ int numTables = tables.size();
+ if (numTables > 0) {
+ cfg.append(prefix).append("routingtable[").append(numTables).append("]\n");
+ for (int i = 0; i < numTables; ++i) {
+ tables.get(i).toConfig(cfg, prefix + "routingtable[" + i + "].");
+ }
+ }
+ }
+
+ /**
+ * Convert a string value to a quoted value suitable for use in a config string.
+ * <p>
+ * Adds double quotes before and after, and adds backslash-escapes to any double quotes that was contained in the
+ * string. A null pointer will produce the special unquoted string null that the config library will convert back
+ * to a null pointer.
+ *
+ * @param input the String to be escaped
+ * @return an escaped String
+ */
+ static String toConfigString(String input) {
+ if (input == null) {
+ return "null";
+ }
+ StringBuilder ret = new StringBuilder(2 + input.length());
+ ret.append("\"");
+ for (int i = 0, len = input.length(); i < len; ++i) {
+ if (input.charAt(i) == '\\') {
+ ret.append("\\\\");
+ } else if (input.charAt(i) == '"') {
+ ret.append("\\\"");
+ } else if (input.charAt(i) == '\n') {
+ ret.append("\\n");
+ } else if (input.charAt(i) == 0) {
+ ret.append("\\x00");
+ } else {
+ ret.append(input.charAt(i));
+ }
+ }
+ ret.append("\"");
+ return ret.toString();
+ }
+
+ // Overrides Object.
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder();
+ toConfig(ret, "");
+ return ret.toString();
+ }
+
+ // Overrides Object.
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RoutingSpec)) {
+ return false;
+ }
+ RoutingSpec rhs = (RoutingSpec)obj;
+ if (!tables.equals(rhs.tables)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = tables != null ? tables.hashCode() : 0;
+ result = 31 * result + (verify ? 1 : 0);
+ return result;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java
new file mode 100644
index 00000000000..724dbc2a642
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java
@@ -0,0 +1,264 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * At any time there may only ever be zero or one routing table registered in message bus for each protocol. This class
+ * contains a list of named hops and routes that may be used to substitute references to these during route resolving.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingTable {
+
+ private final Map<String, HopBlueprint> hops = new LinkedHashMap<String, HopBlueprint>();
+ private final Map<String, Route> routes = new LinkedHashMap<String, Route>();
+
+ /**
+ * Creates a new routing table based on a given specification. This also verifies the integrity of the table.
+ *
+ * @param spec The specification to use.
+ */
+ public RoutingTable(RoutingTableSpec spec) {
+ for (int i = 0; i < spec.getNumHops(); ++i) {
+ HopSpec hopSpec = spec.getHop(i);
+ hops.put(hopSpec.getName(), new HopBlueprint(hopSpec));
+ }
+ for (int i = 0; i < spec.getNumRoutes(); ++i) {
+ RouteSpec routeSpec = spec.getRoute(i);
+ Route route = new Route();
+ for (int j = 0; j < routeSpec.getNumHops(); ++j) {
+ route.addHop(Hop.parse(routeSpec.getHop(j)));
+ }
+ routes.put(routeSpec.getName(), route);
+ }
+ }
+
+ /**
+ * Returns whether or not there are any hops in this routing table.
+ *
+ * @return True if there is at least one hop.
+ */
+ public boolean hasHops() {
+ return !hops.isEmpty();
+ }
+
+ /**
+ * Returns the number of hops that are contained in this.
+ *
+ * @return The number of hops.
+ */
+ public int getNumHops() {
+ return hops.size();
+ }
+
+ /**
+ * Returns an iterator for the hops of this table.
+ *
+ * @return An iterator.
+ */
+ public HopIterator getHopIterator() {
+ return new HopIterator(hops);
+ }
+
+ /**
+ * Returns an iterator for the routes of this table.
+ *
+ * @return An iterator.
+ */
+ public RouteIterator getRouteIterator() {
+ return new RouteIterator(routes);
+ }
+
+ /**
+ * Returns whether or not there are any routes in this routing table.
+ *
+ * @return True if there is at least one route.
+ */
+ public boolean hasRoutes() {
+ return !routes.isEmpty();
+ }
+
+ /**
+ * Returns the number of routes that are contained in this.
+ *
+ * @return The number of routes.
+ */
+ public int getNumRoutes() {
+ return routes.size();
+ }
+
+ /**
+ * Returns whether or not there exists a named hop in this.
+ *
+ * @param name The name of the hop to look for.
+ * @return True if the named hop exists.
+ */
+ public boolean hasHop(String name) {
+ return hops.containsKey(name);
+ }
+
+ /**
+ * Returns the named hop, may be null.
+ *
+ * @param name The name of the hop to return.
+ * @return The hop implementation object.
+ */
+ public HopBlueprint getHop(String name) {
+ return hops.get(name);
+ }
+
+ /**
+ * Returns whether or not there exists a named route in this.
+ *
+ * @param name The name of the route to look for.
+ * @return True if the named route exists.
+ */
+ public boolean hasRoute(String name) {
+ return routes.containsKey(name);
+ }
+
+ /**
+ * Returns the named route, may be null.
+ *
+ * @param name The name of the route to return.
+ * @return The route implementation object.
+ */
+ public Route getRoute(String name) {
+ return routes.get(name);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder("RoutingTable(hops = { ");
+ int i = 0;
+ for (String name : hops.keySet()) {
+ ret.append("'").append(name).append("' : ").append(hops.get(name));
+ if (i++ < hops.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" }, routes = { ");
+ i = 0;
+ for (String name : routes.keySet()) {
+ ret.append("'").append(name).append("' : ").append(routes.get(name));
+ if (i++ < routes.size()) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" })");
+ return ret.toString();
+ }
+
+ /**
+ * Implements an iterator for the hops of this. Use {@link RoutingTable#getHopIterator()}
+ * to retrieve an instance of this.
+ */
+ public static class HopIterator {
+
+ private Iterator<Map.Entry<String, HopBlueprint>> it;
+ private Map.Entry<String, HopBlueprint> entry;
+
+ /**
+ * Constructs a new iterator based on a given map. This is private so that only a {@link RoutingTable} can
+ * create one.
+ *
+ * @param hops The map to iterate through.
+ */
+ private HopIterator(Map<String, HopBlueprint> hops) {
+ it = hops.entrySet().iterator();
+ next();
+ }
+
+ /**
+ * Steps to the next hop in the map.
+ */
+ public void next() {
+ entry = it.hasNext() ? it.next() : null;
+ }
+
+ /**
+ * Returns whether or not this iterator is valid.
+ *
+ * @return True if valid.
+ */
+ public boolean isValid() {
+ return entry != null;
+ }
+
+ /**
+ * Returns the name of the current hop.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return entry.getKey();
+ }
+
+ /**
+ * Returns the current hop.
+ *
+ * @return The hop.
+ */
+ public HopBlueprint getHop() {
+ return entry.getValue();
+ }
+ }
+
+ /**
+ * Implements an iterator for the routes of this. Use {@link RoutingTable#getRouteIterator()}
+ * to retrieve an instance of this.
+ */
+ public static class RouteIterator {
+
+ private Iterator<Map.Entry<String, Route>> it;
+ private Map.Entry<String, Route> entry;
+
+ /**
+ * Constructs a new iterator based on a given map. This is private so that only a {@link RoutingTable} can
+ * create one.
+ *
+ * @param routes The map to iterate through.
+ */
+ private RouteIterator(Map<String, Route> routes) {
+ it = routes.entrySet().iterator();
+ next();
+ }
+
+ /**
+ * Steps to the next route in the map.
+ */
+ public void next() {
+ entry = it.hasNext() ? it.next() : null;
+ }
+
+ /**
+ * Returns whether or not this iterator is valid.
+ *
+ * @return True if valid.
+ */
+ public boolean isValid() {
+ return entry != null;
+ }
+
+ /**
+ * Returns the name of the current route.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return entry.getKey();
+ }
+
+ /**
+ * Returns the current route.
+ *
+ * @return The route.
+ */
+ public Route getRoute() {
+ return entry.getValue();
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTableSpec.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTableSpec.java
new file mode 100644
index 00000000000..c2b3f736c93
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTableSpec.java
@@ -0,0 +1,391 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.text.Utf8String;
+
+import java.util.*;
+
+/**
+ * Along with the {@link RoutingSpec}, {@link RouteSpec} and {@link HopSpec}, this holds the routing specifications for
+ * all protocols. The only way a client can configure or alter the settings of a message bus instance is through these
+ * classes.
+ * <p>
+ * This class contains the spec for a single routing table, which corresponds to exactly one protocol.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingTableSpec {
+
+ private final String protocol;
+ private final List<HopSpec> hops = new ArrayList<>();
+ private final List<RouteSpec> routes = new ArrayList<>();
+ private final boolean verify;
+
+ /**
+ * Creates a new routing table specification for a named protocol.
+ *
+ * @param protocol The name of the protocol that this belongs to.
+ */
+ public RoutingTableSpec(String protocol) {
+ this(protocol, true);
+ }
+ /**
+ * Creates a new routing table specification for a named protocol.
+ *
+ * @param protocol The name of the protocol that this belongs to.
+ */
+ public RoutingTableSpec(Utf8String protocol) {
+ this(protocol.toString(), true);
+ }
+
+ /**
+ * Creates a new routing table specification for a named protocol.
+ *
+ * @param protocol The name of the protocol that this belongs to.
+ * @param verify Whether or not this should be verified.
+ */
+ public RoutingTableSpec(String protocol, boolean verify) {
+ this.protocol = protocol;
+ this.verify = verify;
+ }
+
+ /**
+ * Implements the copy constructor.
+ *
+ * @param obj The object to copy.
+ */
+ public RoutingTableSpec(RoutingTableSpec obj) {
+ this.protocol = obj.protocol;
+ this.verify = obj.verify;
+ for (HopSpec hop : obj.hops) {
+ hops.add(new HopSpec(hop));
+ }
+ for (RouteSpec route : obj.routes) {
+ routes.add(new RouteSpec(route));
+ }
+ }
+
+ /**
+ * Returns the name of the protocol that this is the routing table for.
+ *
+ * @return The protocol name.
+ */
+ public String getProtocol() {
+ return protocol;
+ }
+
+ /**
+ * Returns whether or not there are any hop specs contained in this.
+ *
+ * @return True if there is at least one hop.
+ */
+ public boolean hasHops() {
+ return !hops.isEmpty();
+ }
+
+ /**
+ * Returns whether or not there is a named hop spec contained in this.
+ *
+ * @param hopName The hop name to check for.
+ * @return True if the hop exists.
+ */
+ public boolean hasHop(String hopName) {
+ for (HopSpec hop : hops) {
+ if (hop.getName().equals(hopName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of hops that are contained in this table.
+ *
+ * @return The number of hops.
+ */
+ public int getNumHops() {
+ return hops.size();
+ }
+
+ /**
+ * Returns the hop spec at the given index.
+ *
+ * @param i The index of the hop to return.
+ * @return The hop at the given position.
+ */
+ public HopSpec getHop(int i) {
+ return hops.get(i);
+ }
+
+ /**
+ * Adds the given hop spec to this.
+ *
+ * @param hop The hop to add.
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec addHop(HopSpec hop) {
+ hops.add(hop);
+ return this;
+ }
+
+ /**
+ * Sets the hop spec at the given index.
+ *
+ * @param i The index at which to set the hop.
+ * @param hop The hop to set.
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec setHop(int i, HopSpec hop) {
+ hops.set(i, hop);
+ return this;
+ }
+
+ /**
+ * Removes the hop spec at the given index.
+ *
+ * @param i The index of the hop to remove.
+ * @return The removed hop.
+ */
+ public HopSpec removeHop(int i) {
+ return hops.remove(i);
+ }
+
+ /**
+ * Clears the list of hop specs contained in this.
+ *
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec clearHops() {
+ hops.clear();
+ return this;
+ }
+
+ /**
+ * Returns whether or not there are any route specs contained in this.
+ *
+ * @return True if there is at least one route.
+ */
+ public boolean hasRoutes() {
+ return !routes.isEmpty();
+ }
+
+ /**
+ * Returns whether or not there is a named route spec contained in this.
+ *
+ * @param routeName The hop name to check for.
+ * @return True if the hop exists.
+ */
+ public boolean hasRoute(String routeName) {
+ for (RouteSpec route : routes) {
+ if (route.getName().equals(routeName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of route specs contained in this.
+ *
+ * @return The number of routes.
+ */
+ public int getNumRoutes() {
+ return routes.size();
+ }
+
+ /**
+ * Returns the route spec at the given index.
+ *
+ * @param i The index of the route to return.
+ * @return The route at the given index.
+ */
+ public RouteSpec getRoute(int i) {
+ return routes.get(i);
+ }
+
+ /**
+ * Adds a route spec to this.
+ *
+ * @param route The route to add.
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec addRoute(RouteSpec route) {
+ routes.add(route);
+ return this;
+ }
+
+ /**
+ * Sets the route spec at the given index.
+ *
+ * @param i The index at which to set the route.
+ * @param route The route to set.
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec setRoute(int i, RouteSpec route) {
+ routes.set(i, route);
+ return this;
+ }
+
+ /**
+ * Removes a route spec at a given index.
+ *
+ * @param i The index of the route to remove.
+ * @return The removed route.
+ */
+ public RouteSpec removeRoute(int i) {
+ return routes.remove(i);
+ }
+
+ /**
+ * Clears the list of routes that are contained in this.
+ *
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec clearRoutes() {
+ routes.clear();
+ return this;
+ }
+
+ /**
+ * A convenience function to add a new hop to this routing table.
+ *
+ * @param name A protocol-unique name for this hop.
+ * @param selector A string that represents the selector for this hop.
+ * @param recipients A list of recipients for this hop.
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec addHop(String name, String selector, List<String> recipients) {
+ return addHop(new HopSpec(name, selector).addRecipients(recipients));
+ }
+
+ /**
+ * A convenience function to add a new route to this routing table.
+ *
+ * @param name A protocol-unique name for this route.
+ * @param hops A list of hops for this route.
+ * @return This, to allow chaining.
+ */
+ public RoutingTableSpec addRoute(String name, List<String> hops) {
+ return addRoute(new RouteSpec(name).addHops(hops));
+ }
+
+ /**
+ * Verifies the content of this against the given application.
+ *
+ * @param app The application to verify against.
+ * @param errors The list of errors found.
+ * @return True if no errors where found.
+ */
+ public boolean verify(ApplicationSpec app, List<String> errors) {
+ if (verify) {
+ // Verify and count hops.
+ Map<String, Integer> hopNames = new HashMap<String, Integer>();
+ for (HopSpec hop : hops) {
+ String name = hop.getName();
+ int count = hopNames.containsKey(name) ? hopNames.get(name) : 0;
+ hopNames.put(name, count + 1);
+ hop.verify(app, this, errors);
+ }
+ for (Map.Entry<String, Integer> entry : hopNames.entrySet()) {
+ int count = entry.getValue();
+ if (count > 1) {
+ errors.add("Hop '" + entry.getKey() + "' in routing table '" + protocol + "' is defined " +
+ count + " times.");
+ }
+ }
+
+ // Verify and count routes.
+ Map<String, Integer> routeNames = new HashMap<String, Integer>();
+ for (RouteSpec route : routes) {
+ String name = route.getName();
+ int count = routeNames.containsKey(name) ? routeNames.get(name) : 0;
+ routeNames.put(name, count + 1);
+ route.verify(app, this, errors);
+ }
+ for (Map.Entry<String, Integer> entry : routeNames.entrySet()) {
+ int count = entry.getValue();
+ if (count > 1) {
+ errors.add("Route '" + entry.getKey() + "' in routing table '" + protocol + "' is defined " +
+ count + " times.");
+ }
+ }
+ }
+ return errors.isEmpty();
+ }
+
+ /**
+ * Sorts the hops and routes of this table by name. This is useful for generating a stable config for testing.
+ */
+ public void sort() {
+ Collections.sort(hops, new Comparator<HopSpec>() {
+ public int compare(HopSpec lhs, HopSpec rhs) {
+ return lhs.getName().compareTo(rhs.getName());
+ }
+ });
+ Collections.sort(routes, new Comparator<RouteSpec>() {
+ public int compare(RouteSpec lhs, RouteSpec rhs) {
+ return lhs.getName().compareTo(rhs.getName());
+ }
+ });
+ }
+
+ /**
+ * Appends the content of this to the given config string builder.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ public void toConfig(StringBuilder cfg, String prefix) {
+ cfg.append(prefix).append("protocol ").append(RoutingSpec.toConfigString(protocol)).append("\n");
+ int numHops = hops.size();
+ if (numHops > 0) {
+ cfg.append(prefix).append("hop[").append(numHops).append("]\n");
+ for (int i = 0; i < numHops; ++i) {
+ hops.get(i).toConfig(cfg, prefix + "hop[" + i + "].");
+ }
+ }
+ int numRoutes = routes.size();
+ if (numRoutes > 0) {
+ cfg.append(prefix).append("route[").append(numRoutes).append("]\n");
+ for (int i = 0; i < numRoutes; ++i) {
+ routes.get(i).toConfig(cfg, prefix + "route[" + i + "].");
+ }
+ }
+ }
+
+ // Overrides Object.
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder();
+ toConfig(ret, "");
+ return ret.toString();
+ }
+
+ // Overrides Object.
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RoutingTableSpec)) {
+ return false;
+ }
+ RoutingTableSpec rhs = (RoutingTableSpec)obj;
+ if (!protocol.equals(rhs.protocol)) {
+ return false;
+ }
+ if (!hops.equals(rhs.hops)) {
+ return false;
+ }
+ if (!routes.equals(rhs.routes)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = protocol != null ? protocol.hashCode() : 0;
+ result = 31 * result + (hops != null ? hops.hashCode() : 0);
+ result = 31 * result + (routes != null ? routes.hashCode() : 0);
+ result = 31 * result + (verify ? 1 : 0);
+ return result;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/TcpDirective.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/TcpDirective.java
new file mode 100755
index 00000000000..dd0f6a47596
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/TcpDirective.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * This class represents a tcp directive within a {@link Hop}'s selector. This is a connection string used to establish
+ * a direct connection to a host, bypassing service lookups through Slobrok.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TcpDirective implements HopDirective {
+
+ private final String host;
+ private final int port;
+ private final String session;
+
+ /**
+ * Constructs a new directive to route directly to a tcp address.
+ *
+ * @param host The host name to connect to.
+ * @param port The port to connect to.
+ * @param session The session to route to.
+ */
+ public TcpDirective(String host, int port, String session) {
+ this.host = host;
+ this.port = port;
+ this.session = session;
+ }
+
+ @Override
+ public boolean matches(HopDirective dir) {
+ if (!(dir instanceof TcpDirective)) {
+ return false;
+ }
+ TcpDirective rhs = (TcpDirective)dir;
+ return host.equals(rhs.host) && port == rhs.port && session.equals(rhs.session);
+ }
+
+ /**
+ * Returns the host to connect to. This may be an ip address or a name.
+ *
+ * @return The host.
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Returns the port to connect to on the remove host.
+ *
+ * @return The port number.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Returns the name of the session to route to.
+ *
+ * @return The session name.
+ */
+ public String getSession() {
+ return session;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TcpDirective)) {
+ return false;
+ }
+ TcpDirective rhs = (TcpDirective)obj;
+ if (!host.equals(rhs.host)) {
+ return false;
+ }
+ if (port != rhs.port) {
+ return false;
+ }
+ if (!session.equals(rhs.session)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "tcp/" + host + ":" + port + "/" + session;
+ }
+
+ @Override
+ public String toDebugString() {
+ return "TcpDirective(host = '" + host + "', port = " + port + ", session = '" + session + "')";
+ }
+
+ @Override
+ public int hashCode() {
+ int result = host != null ? host.hashCode() : 0;
+ result = 31 * result + port;
+ result = 31 * result + (session != null ? session.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/VerbatimDirective.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/VerbatimDirective.java
new file mode 100755
index 00000000000..f2f95fa53f8
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/VerbatimDirective.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * This class represents a verbatim match within a {@link Hop}'s selector. This is nothing more than a string that will
+ * be used as-is when performing service name lookups.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class VerbatimDirective implements HopDirective {
+
+ private final String image;
+
+ /**
+ * Constructs a new verbatim selector item for a given image.
+ *
+ * @param image The image to assign to this.
+ */
+ public VerbatimDirective(String image) {
+ this.image = image;
+ }
+
+ @Override
+ public boolean matches(HopDirective dir) {
+
+ return dir instanceof VerbatimDirective && image.equals(((VerbatimDirective)dir).image);
+ }
+
+ /**
+ * Returns the image to which this is a verbatim match.
+ *
+ * @return The image.
+ */
+ public String getImage() {
+ return image;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof VerbatimDirective)) {
+ return false;
+ }
+ VerbatimDirective rhs = (VerbatimDirective)obj;
+ if (!image.equals(rhs.image)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return image != null ? image.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return image;
+ }
+
+ @Override
+ public String toDebugString() {
+ return "VerbatimDirective(image = '" + image + "')";
+ }
+}
+
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/package-info.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/package-info.java
new file mode 100644
index 00000000000..bedb9d96f26
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * This package contains all classes and interfaces that concern routing over message bus.
+ */
+@ExportPackage
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicy.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicy.java
new file mode 100755
index 00000000000..1627679234a
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicy.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing.test;
+
+import com.yahoo.messagebus.EmptyReply;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingContext;
+import com.yahoo.messagebus.routing.RoutingNodeIterator;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CustomPolicy implements RoutingPolicy {
+
+ private boolean selectOnRetry;
+ private final List<Integer> consumableErrors = new ArrayList<Integer>();
+ private final List<Route> routes = new ArrayList<Route>();
+
+ public CustomPolicy(boolean selectOnRetry, List<Integer> consumableErrors, List<Route> routes) {
+ this.selectOnRetry = selectOnRetry;
+ this.consumableErrors.addAll(consumableErrors);
+ this.routes.addAll(routes);
+ }
+
+ public void select(RoutingContext context) {
+ context.trace(1, "Selecting " + routes + ".");
+ context.setSelectOnRetry(selectOnRetry);
+ for (int e : consumableErrors) {
+ context.addConsumableError(e);
+ }
+ context.addChildren(routes);
+ }
+
+ public void merge(RoutingContext context) {
+ List<String> lst = new ArrayList<String>();
+ Reply ret = new EmptyReply();
+ for (RoutingNodeIterator it = context.getChildIterator();
+ it.isValid(); it.next())
+ {
+ lst.add(it.getRoute().toString());
+ Reply reply = it.getReplyRef();
+ for (int i = 0; i < reply.getNumErrors(); ++i) {
+ ret.addError(reply.getError(i));
+ }
+ }
+ context.setReply(ret);
+ context.trace(1, "Merged " + lst + ".");
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicyFactory.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicyFactory.java
new file mode 100755
index 00000000000..60f20b55f8d
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/test/CustomPolicyFactory.java
@@ -0,0 +1,52 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing.test;
+
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+import com.yahoo.messagebus.test.SimpleProtocol;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CustomPolicyFactory implements SimpleProtocol.PolicyFactory {
+
+ private boolean selectOnRetry;
+ private final List<Integer> consumableErrors = new ArrayList<Integer>();
+
+ public CustomPolicyFactory() {
+ this(true);
+ }
+
+ public CustomPolicyFactory(boolean selectOnRetry) {
+ this(selectOnRetry, new ArrayList<Integer>());
+ }
+
+ public CustomPolicyFactory(boolean selectOnRetry, int consumableError) {
+ this(selectOnRetry, Arrays.asList(consumableError));
+ }
+
+ public CustomPolicyFactory(boolean selectOnRetry, List<Integer> consumableErrors) {
+ this.selectOnRetry = selectOnRetry;
+ this.consumableErrors.addAll(consumableErrors);
+ }
+
+ public RoutingPolicy create(String param) {
+ return new CustomPolicy(selectOnRetry, consumableErrors, parseRoutes(param));
+ }
+
+ public static List<Route> parseRoutes(String routes) {
+ List<Route> ret = new ArrayList<Route>();
+ if (routes != null && !routes.isEmpty()) {
+ for (String route : routes.split(",")) {
+ Route r = Route.parse(route);
+ assert(route.equals(r.toString()));
+ ret.add(r);
+ }
+ }
+ return ret;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/test/QueueAdapter.java b/messagebus/src/main/java/com/yahoo/messagebus/test/QueueAdapter.java
new file mode 100644
index 00000000000..25ea51e05dc
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/test/QueueAdapter.java
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.messagebus.*;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class QueueAdapter implements MessageHandler, ReplyHandler {
+
+ private final Queue<Routable> queue = new ConcurrentLinkedQueue<>();
+
+ @Override
+ public void handleMessage(Message message) {
+ queue.offer(message);
+ }
+
+ @Override
+ public void handleReply(Reply reply) {
+ queue.offer(reply);
+ }
+
+ public Routable dequeue() {
+ return queue.poll();
+ }
+
+ public boolean isEmpty() {
+ return queue.isEmpty();
+ }
+
+ public int size() {
+ return queue.size();
+ }
+
+ public boolean waitSize(int size, int seconds) {
+ long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(seconds);
+ while (true) {
+ if (size() == size) {
+ return true;
+ }
+ if (System.currentTimeMillis() > timeout) {
+ return false;
+ }
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/test/Receptor.java b/messagebus/src/main/java/com/yahoo/messagebus/test/Receptor.java
new file mode 100644
index 00000000000..f7b3d5ee9c0
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/test/Receptor.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.messagebus.Message;
+import com.yahoo.messagebus.MessageHandler;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.messagebus.ReplyHandler;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class Receptor implements MessageHandler, ReplyHandler {
+
+ private final BlockingQueue<Message> msg = new LinkedBlockingQueue<>();
+ private final BlockingQueue<Reply> reply = new LinkedBlockingQueue<>();
+
+ public void reset() {
+ msg.clear();
+ reply.clear();
+ }
+
+ public void handleMessage(Message msg) {
+ this.msg.add(msg);
+ }
+
+ public void handleReply(Reply reply) {
+ this.reply.add(reply);
+ }
+
+ public Message getMessage(int seconds) {
+ try {
+ return msg.poll(seconds, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ }
+ }
+
+ public Reply getReply(int seconds) {
+ try {
+ return reply.poll(seconds, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ }
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleMessage.java b/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleMessage.java
new file mode 100644
index 00000000000..17e4375fbca
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleMessage.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.messagebus.Message;
+import com.yahoo.text.Utf8String;
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class SimpleMessage extends Message {
+
+ private String value;
+
+ public SimpleMessage(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public int getType() {
+ return SimpleProtocol.MESSAGE;
+ }
+
+ @Override
+ public Utf8String getProtocol() {
+ return SimpleProtocol.NAME;
+ }
+
+ @Override
+ public int getApproxSize() {
+ return value.length();
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleProtocol.java b/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleProtocol.java
new file mode 100644
index 00000000000..868662f9634
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleProtocol.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.component.Version;
+import com.yahoo.messagebus.EmptyReply;
+import com.yahoo.messagebus.Protocol;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.messagebus.Routable;
+import com.yahoo.messagebus.metrics.MetricSet;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingContext;
+import com.yahoo.messagebus.routing.RoutingNodeIterator;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+import com.yahoo.text.Utf8;
+import com.yahoo.text.Utf8String;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SimpleProtocol implements Protocol {
+
+ public static final Utf8String NAME = new Utf8String("Simple");
+ public static final int MESSAGE = 1;
+ public static final int REPLY = 2;
+ private final Map<String, PolicyFactory> policies = new HashMap<String, PolicyFactory>();
+
+ @Override
+ public String getName() {
+ return NAME.toString();
+ }
+
+ @Override
+ public RoutingPolicy createPolicy(String name, String param) {
+ if (policies.containsKey(name)) {
+ return policies.get(name).create(param);
+ }
+ return null;
+ }
+
+ @Override
+ public Routable decode(Version version, byte[] data) {
+ String str = Utf8.toString(data);
+ if (str.length() < 1) {
+ return null;
+ }
+ char c = str.charAt(0);
+ if (c == 'M') {
+ return new SimpleMessage(str.substring(1));
+ }
+ if (c == 'R') {
+ return new SimpleReply(str.substring(1));
+ }
+ return null;
+ }
+
+ @Override
+ public byte[] encode(Version version, Routable routable) {
+ if (routable.getType() == MESSAGE) {
+ return Utf8.toBytes("M" + ((SimpleMessage)routable).getValue());
+ } else if (routable.getType() == REPLY) {
+ return Utf8.toBytes("R" + ((SimpleReply)routable).getValue());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public MetricSet getMetrics() {
+ return null;
+ }
+
+ /**
+ * Registers a policy factory with this protocol under a given name. Whenever a policy is requested that matches
+ * this name, the factory is invoked.
+ *
+ * @param name The name of the policy.
+ * @param factory The policy factory.
+ */
+ public void addPolicyFactory(String name, PolicyFactory factory) {
+ policies.put(name, factory);
+ }
+
+ /**
+ * Defines a policy factory interface that tests can use to register arbitrary policies with this protocol.
+ */
+ public interface PolicyFactory {
+
+ /**
+ * Creates a new instance of the routing policy that this factory encapsulates.
+ *
+ * @param param The param for the policy constructor.
+ * @return The routing policy created.
+ */
+ public RoutingPolicy create(String param);
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleReply.java b/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleReply.java
new file mode 100644
index 00000000000..47c0a6e48a6
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/test/SimpleReply.java
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.messagebus.Reply;
+import com.yahoo.text.Utf8String;
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class SimpleReply extends Reply {
+
+ private String value;
+
+ public SimpleReply(String value) {
+ this.value = value;
+ }
+
+ public int getType() {
+ return SimpleProtocol.REPLY;
+ }
+
+ public Utf8String getProtocol() {
+ return SimpleProtocol.NAME;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/test/package-info.java b/messagebus/src/main/java/com/yahoo/messagebus/test/package-info.java
new file mode 100644
index 00000000000..7f17252865a
--- /dev/null
+++ b/messagebus/src/main/java/com/yahoo/messagebus/test/package-info.java
@@ -0,0 +1,6 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * This package contains utility classes for the unit tests in the com.yahoo.messagebus package.
+ */
+@com.yahoo.api.annotations.PackageMarker
+package com.yahoo.messagebus.test;
diff --git a/messagebus/src/test/files/.gitignore b/messagebus/src/test/files/.gitignore
new file mode 100644
index 00000000000..b1f2d513ab4
--- /dev/null
+++ b/messagebus/src/test/files/.gitignore
@@ -0,0 +1 @@
+test.cfg
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/ChokeTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/ChokeTestCase.java
new file mode 100755
index 00000000000..cde801d81f2
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/ChokeTestCase.java
@@ -0,0 +1,169 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import junit.framework.TestCase;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ChokeTestCase extends TestCase {
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Setup
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Slobrok slobrok;
+ TestServer srcServer, dstServer;
+ SourceSession srcSession;
+ DestinationSession dstSession;
+
+ @Override
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst")).setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ dstSession = dstServer.mb.createDestinationSession(new DestinationSessionParams().setName("session").setMessageHandler(new Receptor()));
+ srcServer = new TestServer(new MessageBusParams().setRetryPolicy(null).addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setThrottlePolicy(null).setReplyHandler(new Receptor()));
+ assertTrue(srcServer.waitSlobrok("dst/session", 1));
+ }
+
+ @Override
+ public void tearDown() {
+ slobrok.stop();
+ dstSession.destroy();
+ dstServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ public void testMaxCount() {
+ int max = 10;
+ dstServer.mb.setMaxPendingCount(max);
+ List<Message> lst = new ArrayList<>();
+ for (int i = 0; i < max * 2; ++i) {
+ if (i < max) {
+ assertEquals(i, dstServer.mb.getPendingCount());
+ } else {
+ assertEquals(max, dstServer.mb.getPendingCount());
+ }
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ if (i < max) {
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ lst.add(msg);
+ } else {
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.SESSION_BUSY, reply.getError(0).getCode());
+ }
+ }
+ for (int i = 0; i < 5; ++i) {
+ Message msg = lst.remove(0);
+ dstSession.acknowledge(msg);
+
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertFalse(reply.hasErrors());
+ assertNotNull(msg = reply.getMessage());
+ assertTrue(srcSession.send(msg, Route.parse("dst/session")).isAccepted());
+
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ lst.add(msg);
+ }
+ while (!lst.isEmpty()) {
+ assertEquals(lst.size(), dstServer.mb.getPendingCount());
+ Message msg = lst.remove(0);
+ dstSession.acknowledge(msg);
+
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertFalse(reply.hasErrors());
+ }
+ assertEquals(0, dstServer.mb.getPendingCount());
+ }
+
+ public void testMaxSize() {
+ int size = createMessage("msg").getApproxSize();
+ int max = size * 10;
+ dstServer.mb.setMaxPendingSize(max);
+ List<Message> lst = new ArrayList<>();
+ for (int i = 0; i < max * 2; i += size) {
+ if (i < max) {
+ assertEquals(i, dstServer.mb.getPendingSize());
+ } else {
+ assertEquals(max, dstServer.mb.getPendingSize());
+ }
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ if (i < max) {
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ lst.add(msg);
+ } else {
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.SESSION_BUSY, reply.getError(0).getCode());
+ }
+ }
+ for (int i = 0; i < 5; ++i) {
+ Message msg = lst.remove(0);
+ dstSession.acknowledge(msg);
+
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertFalse(reply.hasErrors());
+ assertNotNull(msg = reply.getMessage());
+ assertTrue(srcSession.send(msg, Route.parse("dst/session")).isAccepted());
+
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ lst.add(msg);
+ }
+ while (!lst.isEmpty()) {
+ assertEquals(size * lst.size(), dstServer.mb.getPendingSize());
+ Message msg = lst.remove(0);
+ dstSession.acknowledge(msg);
+
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertFalse(reply.hasErrors());
+ }
+ assertEquals(0, dstServer.mb.getPendingSize());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Utilities
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ private static Message createMessage(String msg) {
+ Message ret = new SimpleMessage(msg);
+ ret.getTrace().setLevel(9);
+ return ret;
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/ConfigAgentTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/ConfigAgentTestCase.java
new file mode 100755
index 00000000000..f6b21e26030
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/ConfigAgentTestCase.java
@@ -0,0 +1,199 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.config.subscription.ConfigSet;
+import com.yahoo.config.subscription.ConfigURI;
+import com.yahoo.messagebus.routing.HopSpec;
+import com.yahoo.messagebus.routing.RouteSpec;
+import com.yahoo.messagebus.routing.RoutingSpec;
+import com.yahoo.messagebus.routing.RoutingTableSpec;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ConfigAgentTestCase {
+
+ @Rule
+ public TemporaryFolder tmpFolder = new TemporaryFolder();
+
+ @Test
+ public void testRoutingConfig() throws InterruptedException, IOException {
+ LocalHandler handler = new LocalHandler();
+ assertFalse(testHalf(handler.spec));
+ assertFalse(testFull(handler.spec));
+
+ ConfigSet set = new ConfigSet();
+ set.addBuilder("test", writeFull());
+
+ ConfigAgent agent = new ConfigAgent(ConfigURI.createFromIdAndSource("test", set), handler);
+ assertFalse(testHalf(handler.spec));
+ assertFalse(testFull(handler.spec));
+ agent.subscribe();
+ assertFalse(testHalf(handler.spec));
+ assertTrue(testFull(handler.spec));
+
+ handler.reset();
+ set.addBuilder("test", writeHalf());
+ assertTrue(handler.await(120, TimeUnit.SECONDS));
+ assertTrue(testHalf(handler.spec));
+ assertFalse(testFull(handler.spec));
+
+ handler.reset();
+ set.addBuilder("test", writeFull());
+ assertTrue(handler.await(120, TimeUnit.SECONDS));
+ assertTrue(testFull(handler.spec));
+ assertFalse(testHalf(handler.spec));
+ }
+
+ private boolean testHalf(RoutingSpec spec) {
+ if (spec.getNumTables() != 1) {
+ return false;
+ }
+ assertTables(spec, 1);
+ return true;
+ }
+
+ private boolean testFull(RoutingSpec spec) {
+ if (spec.getNumTables() != 2) {
+ return false;
+ }
+ assertTables(spec, 2);
+ return true;
+ }
+
+ private void assertTables(RoutingSpec spec, int numTables) {
+ assertEquals(numTables, spec.getNumTables());
+ if (numTables > 0) {
+ assertEquals("foo", spec.getTable(0).getProtocol());
+ assertEquals(2, spec.getTable(0).getNumHops());
+ assertEquals("foo-h1", spec.getTable(0).getHop(0).getName());
+ assertEquals("foo-h1-sel", spec.getTable(0).getHop(0).getSelector());
+ assertEquals(2, spec.getTable(0).getHop(0).getNumRecipients());
+ assertEquals("foo-h1-r1", spec.getTable(0).getHop(0).getRecipient(0));
+ assertEquals("foo-h1-r2", spec.getTable(0).getHop(0).getRecipient(1));
+ assertEquals(true, spec.getTable(0).getHop(0).getIgnoreResult());
+ assertEquals("foo-h2", spec.getTable(0).getHop(1).getName());
+ assertEquals("foo-h2-sel", spec.getTable(0).getHop(1).getSelector());
+ assertEquals(2, spec.getTable(0).getHop(1).getNumRecipients());
+ assertEquals("foo-h2-r1", spec.getTable(0).getHop(1).getRecipient(0));
+ assertEquals("foo-h2-r2", spec.getTable(0).getHop(1).getRecipient(1));
+ assertEquals(2, spec.getTable(0).getNumRoutes());
+ assertEquals("foo-r1", spec.getTable(0).getRoute(0).getName());
+ assertEquals(2, spec.getTable(0).getRoute(0).getNumHops());
+ assertEquals("foo-h1", spec.getTable(0).getRoute(0).getHop(0));
+ assertEquals("foo-h2", spec.getTable(0).getRoute(0).getHop(1));
+ assertEquals("foo-r2", spec.getTable(0).getRoute(1).getName());
+ assertEquals(2, spec.getTable(0).getRoute(1).getNumHops());
+ assertEquals("foo-h2", spec.getTable(0).getRoute(1).getHop(0));
+ assertEquals("foo-h1", spec.getTable(0).getRoute(1).getHop(1));
+ }
+ if (numTables > 1) {
+ assertEquals("bar", spec.getTable(1).getProtocol());
+ assertEquals(2, spec.getTable(1).getNumHops());
+ assertEquals("bar-h1", spec.getTable(1).getHop(0).getName());
+ assertEquals("bar-h1-sel", spec.getTable(1).getHop(0).getSelector());
+ assertEquals(2, spec.getTable(1).getHop(0).getNumRecipients());
+ assertEquals("bar-h1-r1", spec.getTable(1).getHop(0).getRecipient(0));
+ assertEquals("bar-h1-r2", spec.getTable(1).getHop(0).getRecipient(1));
+ assertEquals("bar-h2", spec.getTable(1).getHop(1).getName());
+ assertEquals("bar-h2-sel", spec.getTable(1).getHop(1).getSelector());
+ assertEquals(2, spec.getTable(1).getHop(1).getNumRecipients());
+ assertEquals("bar-h2-r1", spec.getTable(1).getHop(1).getRecipient(0));
+ assertEquals("bar-h2-r2", spec.getTable(1).getHop(1).getRecipient(1));
+ assertEquals(2, spec.getTable(1).getNumRoutes());
+ assertEquals("bar-r1", spec.getTable(1).getRoute(0).getName());
+ assertEquals(2, spec.getTable(1).getRoute(0).getNumHops());
+ assertEquals("bar-h1", spec.getTable(1).getRoute(0).getHop(0));
+ assertEquals("bar-h2", spec.getTable(1).getRoute(0).getHop(1));
+ assertEquals("bar-r2", spec.getTable(1).getRoute(1).getName());
+ assertEquals(2, spec.getTable(1).getRoute(1).getNumHops());
+ assertEquals("bar-h2", spec.getTable(1).getRoute(1).getHop(0));
+ assertEquals("bar-h1", spec.getTable(1).getRoute(1).getHop(1));
+ }
+ }
+
+ private static MessagebusConfig.Builder writeHalf() {
+ return writeTables(1);
+ }
+
+ private static MessagebusConfig.Builder writeFull() {
+ return writeTables(2);
+ }
+
+ private static MessagebusConfig.Builder writeTables(int numTables) {
+ MessagebusConfig.Builder builder = new MessagebusConfig.Builder();
+ if (numTables > 0) {
+ MessagebusConfig.Routingtable.Builder table = new MessagebusConfig.Routingtable.Builder();
+ table.protocol("foo");
+ table.hop(getHop("foo-h1", "foo-h1-sel", "foo-h1-r1", "foo-h1-r2", true));
+ table.hop(getHop("foo-h2", "foo-h2-sel", "foo-h2-r1", "foo-h2-r2", false));
+ table.route(getRoute("foo-r1", "foo-h1", "foo-h2"));
+ table.route(getRoute("foo-r2", "foo-h2", "foo-h1"));
+ builder.routingtable(table);
+ }
+ if (numTables > 1) {
+ MessagebusConfig.Routingtable.Builder table = new MessagebusConfig.Routingtable.Builder();
+ table.protocol("bar");
+ table.hop(getHop("bar-h1", "bar-h1-sel", "bar-h1-r1", "bar-h1-r2", false));
+ table.hop(getHop("bar-h2", "bar-h2-sel", "bar-h2-r1", "bar-h2-r2", false));
+ table.route(getRoute("bar-r1", "bar-h1", "bar-h2"));
+ table.route(getRoute("bar-r2", "bar-h2", "bar-h1"));
+ builder.routingtable(table);
+ }
+ return builder;
+ }
+
+ private static MessagebusConfig.Routingtable.Route.Builder getRoute(String name, String hop1, String hop2) {
+ MessagebusConfig.Routingtable.Route.Builder route = new MessagebusConfig.Routingtable.Route.Builder();
+ route.name(name);
+ route.hop(hop1);
+ route.hop(hop2);
+ return route;
+ }
+
+ private static MessagebusConfig.Routingtable.Hop.Builder getHop(String name, String selector, String recipient1, String recipient2, boolean ignoreresult) {
+ MessagebusConfig.Routingtable.Hop.Builder hop = new MessagebusConfig.Routingtable.Hop.Builder();
+ hop.name(name);
+ hop.selector(selector);
+ hop.recipient(recipient1);
+ hop.recipient(recipient2);
+ hop.ignoreresult(ignoreresult);
+ return hop;
+ }
+
+ private static class LocalHandler implements ConfigHandler {
+
+ volatile RoutingSpec spec = new RoutingSpec();
+
+ public void setupRouting(RoutingSpec spec) {
+ this.spec = spec;
+ }
+
+ public void reset() {
+ spec = null;
+ }
+
+ public boolean await(int timeout, TimeUnit unit) throws InterruptedException {
+ long millis = System.currentTimeMillis() + unit.toMillis(timeout);
+ while (spec == null) {
+ long now = System.currentTimeMillis();
+ if (now >= millis) {
+ return false;
+ }
+ Thread.sleep(1000);
+ }
+ return true;
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/CustomTimer.java b/messagebus/src/test/java/com/yahoo/messagebus/CustomTimer.java
new file mode 100644
index 00000000000..12749ca9bc4
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/CustomTimer.java
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.concurrent.Timer;
+
+/**
+ * @author <a href="mailto:thomasg@yahoo-inc.com">Thomas Gundersen</a>
+ */
+class CustomTimer implements Timer {
+
+ long millis = 0;
+
+ @Override
+ public long milliTime() {
+ return millis;
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/ErrorTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/ErrorTestCase.java
new file mode 100755
index 00000000000..e67fc5030ed
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/ErrorTestCase.java
@@ -0,0 +1,92 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.RoutingTableSpec;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ErrorTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Error err = new Error(69, "foo");
+ assertEquals(69, err.getCode());
+ assertEquals("foo", err.getMessage());
+
+ assertFalse(new Error(ErrorCode.TRANSIENT_ERROR, "foo").isFatal());
+ assertFalse(new Error(ErrorCode.TRANSIENT_ERROR + 1, "foo").isFatal());
+ assertTrue(new Error(ErrorCode.FATAL_ERROR, "foo").isFatal());
+ assertTrue(new Error(ErrorCode.FATAL_ERROR + 1, "foo").isFatal());
+ }
+
+ @Test
+ public void requireThatErrorIsPropagated() throws Exception {
+ RoutingTableSpec table = new RoutingTableSpec(SimpleProtocol.NAME);
+ table.addHop("itr", "test/itr/session", Arrays.asList("test/itr/session"));
+ table.addHop("dst", "test/dst/session", Arrays.asList("test/dst/session"));
+ table.addRoute("test", Arrays.asList("itr", "dst"));
+
+ Slobrok slobrok = new Slobrok();
+ TestServer src = new TestServer("test/src", table, slobrok, null, null);
+ TestServer itr = new TestServer("test/itr", table, slobrok, null, null);
+ TestServer dst = new TestServer("test/dst", table, slobrok, null, null);
+
+ Receptor ss_rr = new Receptor();
+ SourceSession ss = src.mb.createSourceSession(ss_rr);
+
+ Receptor is_mr = new Receptor();
+ Receptor is_rr = new Receptor();
+ IntermediateSession is = itr.mb.createIntermediateSession("session", true, is_mr, is_rr);
+
+ Receptor ds_mr = new Receptor();
+ DestinationSession ds = dst.mb.createDestinationSession("session", true, ds_mr);
+
+ src.waitSlobrok("test/itr/session", 1);
+ src.waitSlobrok("test/dst/session", 1);
+ itr.waitSlobrok("test/dst/session", 1);
+
+ for (int i = 0; i < 5; i++) {
+ assertTrue(ss.send(new SimpleMessage("msg"), "test").isAccepted());
+ Message msg = is_mr.getMessage(60);
+ assertNotNull(msg);
+ is.forward(msg);
+
+ assertNotNull(msg = ds_mr.getMessage(60));
+ Reply reply = new EmptyReply();
+ msg.swapState(reply);
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR, "fatality"));
+ ds.reply(reply);
+
+ assertNotNull(reply = is_rr.getReply(60));
+ assertEquals(reply.getNumErrors(), 1);
+ assertEquals(reply.getError(0).getService(), "test/dst/session");
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR, "fatality"));
+ is.forward(reply);
+
+ assertNotNull(reply = ss_rr.getReply(60));
+ assertEquals(reply.getNumErrors(), 2);
+ assertEquals(reply.getError(0).getService(), "test/dst/session");
+ assertEquals(reply.getError(1).getService(), "test/itr/session");
+ }
+
+ ss.destroy();
+ is.destroy();
+ ds.destroy();
+
+ dst.destroy();
+ itr.destroy();
+ src.destroy();
+ slobrok.stop();
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/MessageBusTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/MessageBusTestCase.java
new file mode 100644
index 00000000000..95b4e3663e8
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/MessageBusTestCase.java
@@ -0,0 +1,144 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.rpc.RPCNetwork;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.RetryTransientErrorsPolicy;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+import com.yahoo.messagebus.routing.test.CustomPolicyFactory;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import org.junit.Test;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class MessageBusTestCase {
+
+ @Test
+ public void requireThatBucketSequencingWithResenderEnabledCausesError() throws ListenFailedException {
+ Slobrok slobrok = new Slobrok();
+ TestServer server = new TestServer(new MessageBusParams()
+ .addProtocol(new SimpleProtocol())
+ .setRetryPolicy(new RetryTransientErrorsPolicy()),
+ new RPCNetworkParams()
+ .setSlobrokConfigId(slobrok.configId()));
+ Receptor receptor = new Receptor();
+ SourceSession session = server.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(receptor));
+ assertTrue(session.send(new SimpleMessage("foo") {
+ @Override
+ public boolean hasBucketSequence() {
+ return true;
+ }
+ }.setRoute(Route.parse("bar"))).isAccepted());
+ Reply reply = receptor.getReply(60);
+ assertNotNull(reply);
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.SEQUENCE_ERROR, reply.getError(0).getCode());
+ session.destroy();
+ server.destroy();
+ slobrok.stop();
+ }
+
+ @Test
+ public void testConnectionSpec() throws ListenFailedException, UnknownHostException {
+ // Setup servers and sessions.
+ Slobrok slobrok = new Slobrok();
+ List<TestServer> servers = new ArrayList<>();
+
+ TestServer srcServer = new TestServer("feeder", null, slobrok, null, null);
+ servers.add(srcServer);
+ SourceSession src = servers.get(0).mb.createSourceSession(new Receptor());
+
+ List<IntermediateSession> sessions = new ArrayList<>();
+ for (int i = 0; i < 10; ++i) {
+ TestServer server = new TestServer("intermediate/" + i, null, slobrok, null, null);
+ servers.add(server);
+ sessions.add(server.mb.createIntermediateSession("session", true, new Receptor(), new Receptor()));
+ }
+
+ TestServer dstServer = new TestServer("destination", null, slobrok, null, null);
+ DestinationSession dst = dstServer.mb.createDestinationSession("session", true, new Receptor());
+
+ assertTrue(srcServer.waitSlobrok("intermediate/*/session", sessions.size()));
+ assertTrue(srcServer.waitSlobrok("destination/session", 1));
+
+ StringBuilder route = new StringBuilder();
+ for (int i = 0; i < sessions.size(); i++) {
+ route.append("intermediate/").append(i).append("/session ");
+ route.append(sessions.get(i).getConnectionSpec()).append(" ");
+ }
+ route.append(dst.getConnectionSpec());
+
+ Message msg = new SimpleMessage("empty");
+ assertTrue(src.send(msg, Route.parse(route.toString())).isAccepted());
+ for (IntermediateSession itr : sessions) {
+ // Received using session name.
+ assertNotNull(msg = ((Receptor)itr.getMessageHandler()).getMessage(60));
+ itr.forward(msg);
+
+ // Received using connection spec.
+ assertNotNull(msg = ((Receptor)itr.getMessageHandler()).getMessage(60));
+ itr.forward(msg);
+ }
+ assertNotNull(msg = ((Receptor)dst.getMessageHandler()).getMessage(60));
+ dst.acknowledge(msg);
+ for (int i = sessions.size(); --i >= 0;) {
+ IntermediateSession itr = sessions.get(i);
+
+ // Received for connection spec.
+ Reply reply = ((Receptor)itr.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ itr.forward(reply);
+
+ // Received for session name.
+ assertNotNull(reply = ((Receptor)itr.getReplyHandler()).getReply(60));
+ itr.forward(reply);
+ }
+ assertNotNull(((Receptor)src.getReplyHandler()).getReply(60));
+
+ // Cleanup.
+ for (IntermediateSession session : sessions) {
+ session.destroy();
+ }
+ for (TestServer server : servers) {
+ server.destroy();
+ }
+ slobrok.stop();
+ }
+
+ @Test
+ public void testRoutingPolicyCache() throws ListenFailedException, UnknownHostException {
+ Slobrok slobrok = new Slobrok();
+ String config = "slobrok[1]\nslobrok[0].connectionspec \"" + new Spec("localhost", slobrok.port()).toString() + "\"\n";
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory());
+ MessageBus bus = new MessageBus(new RPCNetwork(new RPCNetworkParams().setSlobrokConfigId("raw:" + config)),
+ new MessageBusParams().addProtocol(protocol));
+
+ RoutingPolicy all = bus.getRoutingPolicy(SimpleProtocol.NAME, "Custom", null);
+ assertNotNull(all);
+
+ RoutingPolicy ref = bus.getRoutingPolicy(SimpleProtocol.NAME, "Custom", null);
+ assertNotNull(ref);
+ assertSame(all, ref);
+
+ RoutingPolicy allArg = bus.getRoutingPolicy(SimpleProtocol.NAME, "Custom", "Arg");
+ assertNotNull(allArg);
+ assertNotSame(all, allArg);
+
+ RoutingPolicy refArg = bus.getRoutingPolicy(SimpleProtocol.NAME, "Custom", "Arg");
+ assertNotNull(refArg);
+ assertSame(allArg, refArg);
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/MessengerTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/MessengerTestCase.java
new file mode 100644
index 00000000000..37a99381cd1
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/MessengerTestCase.java
@@ -0,0 +1,124 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MessengerTestCase {
+
+ @Test
+ public void requireThatSyncWithSelfDoesNotCauseDeadLock() throws InterruptedException {
+ final Messenger msn = new Messenger();
+ msn.start();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ msn.enqueue(new Messenger.Task() {
+
+ @Override
+ public void run() {
+ msn.sync();
+ }
+
+ @Override
+ public void destroy() {
+ latch.countDown();
+ }
+ });
+ assertTrue(latch.await(60, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatTaskIsExecuted() throws InterruptedException {
+ Messenger msn = new Messenger();
+ msn.start();
+ assertTrue(tryMessenger(msn));
+ }
+
+ @Test
+ public void requireThatRunExceptionIsCaught() throws InterruptedException {
+ Messenger msn = new Messenger();
+ msn.start();
+ msn.enqueue(new Messenger.Task() {
+ @Override
+ public void run() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ });
+ assertTrue(tryMessenger(msn));
+ }
+
+ @Test
+ public void requireThatDestroyExceptionIsCaught() throws InterruptedException {
+ Messenger msn = new Messenger();
+ msn.start();
+ msn.enqueue(new Messenger.Task() {
+ @Override
+ public void run() {
+
+ }
+
+ @Override
+ public void destroy() {
+ throw new RuntimeException();
+ }
+ });
+ assertTrue(tryMessenger(msn));
+ }
+
+ @Test
+ public void requireThatRunAndDestroyExceptionsAreCaught() throws InterruptedException {
+ Messenger msn = new Messenger();
+ msn.start();
+ msn.enqueue(new Messenger.Task() {
+ @Override
+ public void run() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public void destroy() {
+ throw new RuntimeException();
+ }
+ });
+ assertTrue(tryMessenger(msn));
+ }
+
+ private static boolean tryMessenger(Messenger msn) {
+ MyTask task = new MyTask();
+ msn.enqueue(task);
+ try {
+ return task.runLatch.await(60, TimeUnit.SECONDS) &&
+ task.destroyLatch.await(60, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+
+ private static class MyTask implements Messenger.Task {
+
+ final CountDownLatch runLatch = new CountDownLatch(1);
+ final CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ @Override
+ public void run() {
+ runLatch.countDown();
+ }
+
+ @Override
+ public void destroy() {
+ destroyLatch.countDown();
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/ProtocolRepositoryTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/ProtocolRepositoryTestCase.java
new file mode 100644
index 00000000000..ddb21baadab
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/ProtocolRepositoryTestCase.java
@@ -0,0 +1,103 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.messagebus.routing.RoutingContext;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ProtocolRepositoryTestCase {
+
+ @Test
+ public void requireThatPolicyCanBeNull() {
+ ProtocolRepository repo = new ProtocolRepository();
+ SimpleProtocol protocol = new SimpleProtocol();
+ repo.putProtocol(protocol);
+ assertNull(repo.getRoutingPolicy(SimpleProtocol.NAME, "Custom", null));
+ }
+
+ @Test
+ public void requireThatPolicyCanBeCreated() {
+ ProtocolRepository repo = new ProtocolRepository();
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new MyFactory());
+ repo.putProtocol(protocol);
+ assertNotNull(repo.getRoutingPolicy(SimpleProtocol.NAME, "Custom", null));
+ }
+
+ @Test
+ public void requireThatPolicyIsCached() {
+ ProtocolRepository repo = new ProtocolRepository();
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new MyFactory());
+ repo.putProtocol(protocol);
+
+ RoutingPolicy prev = repo.getRoutingPolicy(SimpleProtocol.NAME, "Custom", null);
+ assertNotNull(prev);
+
+ RoutingPolicy next = repo.getRoutingPolicy(SimpleProtocol.NAME, "Custom", null);
+ assertNotNull(next);
+ assertSame(prev, next);
+ }
+
+ @Test
+ public void requireThatPolicyParamIsPartOfCacheKey() {
+ ProtocolRepository repo = new ProtocolRepository();
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new MyFactory());
+ repo.putProtocol(protocol);
+
+ RoutingPolicy prev = repo.getRoutingPolicy(SimpleProtocol.NAME, "Custom", "foo");
+ assertNotNull(prev);
+
+ RoutingPolicy next = repo.getRoutingPolicy(SimpleProtocol.NAME, "Custom", "bar");
+ assertNotNull(next);
+ assertNotSame(prev, next);
+ }
+
+ @Test
+ public void requireThatCreatePolicyExceptionIsCaught() {
+ ProtocolRepository repo = new ProtocolRepository();
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ throw new RuntimeException();
+ }
+ });
+ repo.putProtocol(protocol);
+ assertNull(repo.getRoutingPolicy(SimpleProtocol.NAME, "Custom", null));
+ }
+
+ private static class MyFactory implements SimpleProtocol.PolicyFactory {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new MyPolicy();
+ }
+ }
+
+ private static class MyPolicy implements RoutingPolicy {
+
+ @Override
+ public void select(RoutingContext context) {
+
+ }
+
+ @Override
+ public void merge(RoutingContext context) {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/RateThrottlingTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/RateThrottlingTestCase.java
new file mode 100644
index 00000000000..dc4beb5cf9f
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/RateThrottlingTestCase.java
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.messagebus.test.SimpleMessage;
+import junit.framework.TestCase;
+
+public class RateThrottlingTestCase extends TestCase {
+
+ public void testPending() {
+ CustomTimer timer = new CustomTimer();
+ RateThrottlingPolicy policy = new RateThrottlingPolicy(5.0, timer);
+ policy.setMaxPendingCount(200);
+
+ // Check that we obey the max still.
+ assertFalse(policy.canSend(new SimpleMessage("test"), 300));
+ }
+
+ public int getActualRate(double desiredRate) {
+ CustomTimer timer = new CustomTimer();
+ RateThrottlingPolicy policy = new RateThrottlingPolicy(desiredRate, timer);
+
+ int ok = 0;
+ for (int i = 0; i < 10000; ++i) {
+ if (policy.canSend(new SimpleMessage("test"), 0)) {
+ ok++;
+ }
+ timer.millis += 10;
+ }
+
+ return ok;
+ }
+
+ public void testRates() {
+ assertEquals(10, getActualRate(0.1), 1);
+ assertEquals(1000, getActualRate(10), 100);
+ assertEquals(500, getActualRate(5), 50);
+ assertEquals(100, getActualRate(1), 10);
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/RoutableTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/RoutableTestCase.java
new file mode 100755
index 00000000000..4e76ab86889
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/RoutableTestCase.java
@@ -0,0 +1,114 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleReply;
+
+import java.net.UnknownHostException;
+
+public class RoutableTestCase extends junit.framework.TestCase {
+
+ public void testMessageContext() throws ListenFailedException, UnknownHostException {
+ Slobrok slobrok = new Slobrok();
+ TestServer srcServer = new TestServer("src", null, slobrok, null, null);
+ TestServer dstServer = new TestServer("dst", null, slobrok, null, null);
+ SourceSession srcSession = srcServer.mb.createSourceSession(
+ new Receptor(),
+ new SourceSessionParams().setTimeout(600.0));
+ DestinationSession dstSession = dstServer.mb.createDestinationSession("session", true, new Receptor());
+
+ assertTrue(srcServer.waitSlobrok("dst/session", 1));
+
+ Object context = new Object();
+ Message msg = new SimpleMessage("msg");
+ msg.setContext(context);
+ assertTrue(srcSession.send(msg, "dst/session", true).isAccepted());
+
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ dstSession.acknowledge(msg);
+
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertSame(reply.getContext(), context);
+
+ srcSession.destroy();
+ srcServer.destroy();
+ dstSession.destroy();
+ dstServer.destroy();
+ slobrok.stop();
+ }
+
+ public void testMessageSwapState() {
+ Message foo = new SimpleMessage("foo");
+ Route fooRoute = Route.parse("foo");
+ foo.setRoute(fooRoute);
+ foo.setRetry(1);
+ foo.setTimeReceivedNow();
+ foo.setTimeRemaining(2);
+
+ Message bar = new SimpleMessage("bar");
+ Route barRoute = Route.parse("bar");
+ bar.setRoute(barRoute);
+ bar.setRetry(3);
+ bar.setTimeReceivedNow();
+ bar.setTimeRemaining(4);
+
+ foo.swapState(bar);
+ assertEquals(barRoute, foo.getRoute());
+ assertEquals(fooRoute, bar.getRoute());
+ assertEquals(3, foo.getRetry());
+ assertEquals(1, bar.getRetry());
+ assertTrue(foo.getTimeReceived() >= bar.getTimeReceived());
+ assertEquals(4, foo.getTimeRemaining());
+ assertEquals(2, bar.getTimeRemaining());
+ }
+
+ public void testReplySwapState() {
+ Reply foo = new SimpleReply("foo");
+ Message fooMsg = new SimpleMessage("foo");
+ foo.setMessage(fooMsg);
+ foo.setRetryDelay(1);
+ foo.addError(new Error(ErrorCode.APP_FATAL_ERROR, "fatal"));
+ foo.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "transient"));
+
+ Reply bar = new SimpleReply("bar");
+ Message barMsg = new SimpleMessage("bar");
+ bar.setMessage(barMsg);
+ bar.setRetryDelay(2);
+ bar.addError(new Error(ErrorCode.ERROR_LIMIT, "err"));
+
+ foo.swapState(bar);
+ assertEquals(barMsg, foo.getMessage());
+ assertEquals(fooMsg, bar.getMessage());
+ assertEquals(2.0, foo.getRetryDelay());
+ assertEquals(1.0, bar.getRetryDelay());
+ assertEquals(1, foo.getNumErrors());
+ assertEquals(2, bar.getNumErrors());
+ }
+
+ public void testMessageDiscard() {
+ Receptor handler = new Receptor();
+ Message msg = new SimpleMessage("foo");
+ msg.pushHandler(handler);
+ msg.discard();
+
+ assertNull(handler.getReply(0));
+ }
+
+ public void testReplyDiscard() {
+ Receptor handler = new Receptor();
+ Message msg = new SimpleMessage("foo");
+ msg.pushHandler(handler);
+
+ Reply reply = new SimpleReply("bar");
+ reply.swapState(msg);
+ reply.discard();
+
+ assertNull(handler.getReply(0));
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/SendProxyTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/SendProxyTestCase.java
new file mode 100644
index 00000000000..6eb7257328b
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/SendProxyTestCase.java
@@ -0,0 +1,169 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.component.Vtag;
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.log.LogLevel;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import com.yahoo.messagebus.test.SimpleReply;
+import junit.framework.TestCase;
+
+import java.net.UnknownHostException;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SendProxyTestCase extends TestCase {
+
+ Slobrok slobrok;
+ TestServer srcServer, dstServer;
+ SourceSession srcSession;
+ DestinationSession dstSession;
+
+ @Override
+ public void setUp() throws UnknownHostException, ListenFailedException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst")).setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ dstSession = dstServer.mb.createDestinationSession(new DestinationSessionParams().setName("session").setMessageHandler(new Receptor()));
+ srcServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setThrottlePolicy(null).setReplyHandler(new Receptor()));
+ assertTrue(srcServer.waitSlobrok("dst/session", 1));
+ }
+
+ @Override
+ public void tearDown() {
+ slobrok.stop();
+ dstSession.destroy();
+ dstServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+ }
+
+ public void testTraceByLogLevel() {
+ Logger log = Logger.getLogger(SendProxy.class.getName());
+ LogHandler logHandler = new LogHandler();
+ log.addHandler(logHandler);
+
+ log.setLevel(LogLevel.INFO);
+ sendMessage(0, null);
+ assertNull(logHandler.trace);
+
+ log.setLevel(LogLevel.DEBUG);
+ sendMessage(0, null);
+ assertNull(logHandler.trace);
+
+ sendMessage(1, new Error(ErrorCode.FATAL_ERROR, "err"));
+ assertNull(logHandler.trace);
+
+ sendMessage(0, new Error(ErrorCode.FATAL_ERROR, "err"));
+ assertEquals("Trace for reply with error(s):\n" +
+ "<trace>\n" +
+ " <trace>\n" +
+ " Sending message (version ${VERSION}) from client to 'dst/session' with x seconds timeout.\n" +
+ " <trace>\n" +
+ " Message (type 1) received at 'dst' for session 'session'.\n" +
+ " [FATAL_ERROR @ localhost]: err\n" +
+ " Sending reply (version ${VERSION}) from 'dst'.\n" +
+ " </trace>\n" +
+ " Reply (type 2) received at client.\n" +
+ " </trace>\n" +
+ "</trace>\n", logHandler.trace);
+ logHandler.trace = null;
+
+ log.setLevel(LogLevel.SPAM);
+ sendMessage(1, null);
+ assertNull(logHandler.trace);
+
+ sendMessage(0, null);
+ assertEquals("Trace for reply:\n" +
+ "<trace>\n" +
+ " <trace>\n" +
+ " Sending message (version ${VERSION}) from client to 'dst/session' with x seconds timeout.\n" +
+ " <trace>\n" +
+ " Message (type 1) received at 'dst' for session 'session'.\n" +
+ " Sending reply (version ${VERSION}) from 'dst'.\n" +
+ " </trace>\n" +
+ " Reply (type 0) received at client.\n" +
+ " </trace>\n" +
+ "</trace>\n", logHandler.trace);
+ logHandler.trace = null;
+
+ sendMessage(1, new Error(ErrorCode.FATAL_ERROR, "err"));
+ assertNull(logHandler.trace);
+
+ sendMessage(0, new Error(ErrorCode.FATAL_ERROR, "err"));
+ assertEquals("Trace for reply with error(s):\n" +
+ "<trace>\n" +
+ " <trace>\n" +
+ " Sending message (version ${VERSION}) from client to 'dst/session' with x seconds timeout.\n" +
+ " <trace>\n" +
+ " Message (type 1) received at 'dst' for session 'session'.\n" +
+ " [FATAL_ERROR @ localhost]: err\n" +
+ " Sending reply (version ${VERSION}) from 'dst'.\n" +
+ " </trace>\n" +
+ " Reply (type 2) received at client.\n" +
+ " </trace>\n" +
+ "</trace>\n", logHandler.trace);
+ logHandler.trace = null;
+ }
+
+ private void sendMessage(int traceLevel, Error err) {
+ Message msg = new SimpleMessage("foo");
+ msg.getTrace().setLevel(traceLevel);
+ assertTrue(srcSession.send(msg, Route.parse("dst/session")).isAccepted());
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ if (err != null) {
+ Reply reply = new SimpleReply("bar");
+ reply.swapState(msg);
+ reply.addError(err);
+ dstSession.reply(reply);
+ } else {
+ dstSession.acknowledge(msg);
+ }
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ }
+
+ private static class LogHandler extends Handler {
+
+ String trace = null;
+
+ @Override
+ public void publish(LogRecord record) {
+ String msg = record.getMessage();
+ if (msg.startsWith("Trace ")) {
+ msg = msg.replaceAll("\\[.*\\] ", "");
+ msg = msg.replaceAll("[0-9]+\\.[0-9]+ seconds", "x seconds");
+
+ String ver = Vtag.currentVersion.toString();
+ for (int i = msg.indexOf(ver); i >= 0; i = msg.indexOf(ver, i)) {
+ msg = msg.substring(0, i) + "${VERSION}" + msg.substring(i + ver.length());
+ }
+ trace = msg;
+ }
+ }
+
+ @Override
+ public void flush() {
+ // empty
+ }
+
+ @Override
+ public void close() throws SecurityException {
+ // empty
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/SequencerTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/SequencerTestCase.java
new file mode 100644
index 00000000000..bd053e5ad63
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/SequencerTestCase.java
@@ -0,0 +1,179 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.messagebus.test.SimpleMessage;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SequencerTestCase extends junit.framework.TestCase {
+
+ public void testSyncNone() {
+ TestQueue src = new TestQueue();
+ TestQueue dst = new TestQueue();
+ QueueSender sender = new QueueSender(dst);
+ Sequencer seq = new Sequencer(sender);
+
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ assertEquals(0, src.size());
+ assertEquals(5, dst.size());
+
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ assertEquals(5, src.size());
+ assertEquals(0, dst.size());
+
+ src.checkReply(false, 0);
+ src.checkReply(false, 0);
+ src.checkReply(false, 0);
+ src.checkReply(false, 0);
+ src.checkReply(false, 0);
+ assertEquals(0, src.size());
+ assertEquals(0, dst.size());
+ }
+
+ public void testSyncId() {
+ TestQueue src = new TestQueue();
+ TestQueue dst = new TestQueue();
+ QueueSender sender = new QueueSender(dst);
+ Sequencer seq = new Sequencer(sender);
+
+ seq.handleMessage(src.createMessage(true, 1L));
+ seq.handleMessage(src.createMessage(true, 2L));
+ seq.handleMessage(src.createMessage(true, 3L));
+ seq.handleMessage(src.createMessage(true, 4L));
+ seq.handleMessage(src.createMessage(true, 5L));
+ assertEquals(0, src.size());
+ assertEquals(5, dst.size());
+
+ seq.handleMessage(src.createMessage(true, 1L));
+ seq.handleMessage(src.createMessage(true, 5L));
+ seq.handleMessage(src.createMessage(true, 2L));
+ seq.handleMessage(src.createMessage(true, 10L));
+ seq.handleMessage(src.createMessage(true, 4L));
+ seq.handleMessage(src.createMessage(true, 3L));
+ assertEquals(0, src.size());
+ assertEquals(6, dst.size());
+
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ assertEquals(5, src.size());
+ assertEquals(6, dst.size());
+
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ assertEquals(11, src.size());
+ assertEquals(0, dst.size());
+
+ src.checkReply(true, 1);
+ src.checkReply(true, 2);
+ src.checkReply(true, 3);
+ src.checkReply(true, 4);
+ src.checkReply(true, 5);
+ src.checkReply(true, 10);
+ src.checkReply(true, 1);
+ src.checkReply(true, 2);
+ src.checkReply(true, 3);
+ src.checkReply(true, 4);
+ src.checkReply(true, 5);
+ assertEquals(0, src.size());
+ assertEquals(0, dst.size());
+ }
+
+ @SuppressWarnings("serial")
+ private static class TestQueue extends LinkedList<Routable> implements ReplyHandler {
+
+ void checkReply(boolean hasSeqId, long seqId) {
+ if (size() == 0) {
+ throw new IllegalStateException("No routable in queue.");
+ }
+ Routable obj = remove();
+ assertTrue(obj instanceof Reply);
+
+ Reply reply = (Reply)obj;
+ Message msg = reply.getMessage();
+ assertNotNull(msg);
+
+ assertEquals(hasSeqId, msg.hasSequenceId());
+ if (hasSeqId) {
+ assertEquals(seqId, msg.getSequenceId());
+ }
+ }
+
+ public void handleReply(Reply reply) {
+ add(reply);
+ }
+
+ void replyNext() {
+ Routable obj = remove();
+ assertTrue(obj instanceof Message);
+ Message msg = (Message)obj;
+
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.setMessage(msg);
+ ReplyHandler handler = reply.popHandler();
+ handler.handleReply(reply);
+ }
+
+ Message createMessage(final boolean hasSeqId, final long seqId) {
+ Message ret = new MyMessage(hasSeqId, seqId);
+ ret.pushHandler(this);
+ return ret;
+ }
+ }
+
+ private static class QueueSender implements MessageHandler {
+
+ Queue<Routable> queue;
+
+ QueueSender(Queue<Routable> queue) {
+ this.queue = queue;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ queue.offer(msg);
+ }
+ }
+
+ private static class MyMessage extends SimpleMessage {
+
+ final boolean hasSeqId;
+ final long seqId;
+
+ MyMessage(boolean hasSeqId, long seqId) {
+ super("foo");
+ this.hasSeqId = hasSeqId;
+ this.seqId = seqId;
+ }
+
+ @Override
+ public boolean hasSequenceId() {
+ return hasSeqId;
+ }
+
+ @Override
+ public long getSequenceId() {
+ return seqId;
+ }
+ }
+}
+
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/SimpleTripTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/SimpleTripTestCase.java
new file mode 100755
index 00000000000..2795758d922
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/SimpleTripTestCase.java
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import com.yahoo.messagebus.test.SimpleReply;
+
+import java.net.UnknownHostException;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SimpleTripTestCase extends junit.framework.TestCase {
+
+ public void testSimpleTrip() throws ListenFailedException, UnknownHostException {
+ Slobrok slobrok = new Slobrok();
+ TestServer server = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams()
+ .setIdentity(new Identity("srv"))
+ .setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ DestinationSession dst = server.mb.createDestinationSession(new DestinationSessionParams().setName("session").setMessageHandler(new Receptor()));
+ SourceSession src = server.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(new Receptor()));
+ assertTrue(server.waitSlobrok("srv/session", 1));
+
+ assertTrue(src.send(new SimpleMessage("msg"), Route.parse("srv/session")).isAccepted());
+ Message msg = ((Receptor)dst.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ assertEquals(SimpleProtocol.NAME, msg.getProtocol());
+ assertEquals(SimpleProtocol.MESSAGE, msg.getType());
+ assertEquals("msg", ((SimpleMessage)msg).getValue());
+
+ Reply reply = new SimpleReply("reply");
+ reply.swapState(msg);
+ dst.reply(reply);
+
+ assertNotNull(reply = ((Receptor)src.getReplyHandler()).getReply(60));
+ assertEquals(SimpleProtocol.NAME, reply.getProtocol());
+ assertEquals(SimpleProtocol.REPLY, reply.getType());
+ assertEquals("reply", ((SimpleReply)reply).getValue());
+
+ src.destroy();
+ dst.destroy();
+ server.destroy();
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/ThrottlerTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/ThrottlerTestCase.java
new file mode 100644
index 00000000000..405aa1c0b96
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/ThrottlerTestCase.java
@@ -0,0 +1,230 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.RoutingTableSpec;
+import com.yahoo.messagebus.test.*;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ThrottlerTestCase extends junit.framework.TestCase {
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Setup
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Slobrok slobrok;
+ TestServer src, dst;
+
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ RoutingTableSpec table = new RoutingTableSpec(SimpleProtocol.NAME);
+ table.addHop("dst", "test/dst/session", Arrays.asList("test/dst/session"));
+ table.addRoute("test", Arrays.asList("dst"));
+ slobrok = new Slobrok();
+ src = new TestServer("test/src", table, slobrok, null, null);
+ dst = new TestServer("test/dst", table, slobrok, null, null);
+ }
+
+ public void tearDown() {
+ dst.destroy();
+ src.destroy();
+ slobrok.stop();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ public void testMaxCount() {
+ // Prepare a source session with throttle enabled.
+ SourceSessionParams params = new SourceSessionParams().setTimeout(600.0);
+ StaticThrottlePolicy policy = new StaticThrottlePolicy();
+ policy.setMaxPendingCount(10);
+ params.setThrottlePolicy(policy);
+
+ Receptor src_rr = new Receptor();
+ SourceSession src_s = src.mb.createSourceSession(src_rr, params);
+
+ // Prepare a destination session to acknowledge messages.
+ QueueAdapter dst_q = new QueueAdapter();
+ DestinationSession dst_s = dst.mb.createDestinationSession("session", true, dst_q);
+ src.waitSlobrok("test/dst/session", 1);
+
+ // Send until throttler rejects a message.
+ for (int i = 0; i < policy.getMaxPendingCount(); i++) {
+ assertTrue(src_s.send(new SimpleMessage("msg"), "test").isAccepted());
+ }
+ assertFalse(src_s.send(new SimpleMessage("msg"), "test").isAccepted());
+
+ // Acknowledge one message at a time, then attempt to send two more.
+ for (int i = 0; i < 10; i++) {
+ assertTrue(dst_q.waitSize(policy.getMaxPendingCount(), 60));
+ dst_s.acknowledge((Message)dst_q.dequeue());
+
+ assertNotNull(src_rr.getReply(60));
+ assertTrue(src_s.send(new SimpleMessage("msg"), "test").isAccepted());
+ assertFalse(src_s.send(new SimpleMessage("msg"), "test").isAccepted());
+ }
+
+ assertTrue(dst_q.waitSize(policy.getMaxPendingCount(), 60));
+ while (!dst_q.isEmpty()) {
+ dst_s.acknowledge((Message)dst_q.dequeue());
+ }
+
+ src_s.close();
+ dst_s.destroy();
+ }
+
+ public void testMaxSize() {
+ // Prepare a source session with throttle enabled.
+ SourceSessionParams params = new SourceSessionParams().setTimeout(600.0);
+ StaticThrottlePolicy policy = new StaticThrottlePolicy();
+ policy.setMaxPendingCount(1000);
+ policy.setMaxPendingSize(2);
+ params.setThrottlePolicy(policy);
+
+ Receptor src_rr = new Receptor();
+ SourceSession src_s = src.mb.createSourceSession(src_rr, params);
+
+ // Prepare a destination session to acknowledge messages.
+ QueueAdapter dst_q = new QueueAdapter();
+ DestinationSession dst_s = dst.mb.createDestinationSession("session", true, dst_q);
+ src.waitSlobrok("test/dst/session", 1);
+
+ assertTrue(src_s.send(new SimpleMessage("1"), "test").isAccepted());
+ assertTrue(dst_q.waitSize(1, 60));
+ assertTrue(src_s.send(new SimpleMessage("12"), "test").isAccepted());
+ assertTrue(dst_q.waitSize(2, 60));
+
+ assertFalse(src_s.send(new SimpleMessage("1"), "test").isAccepted());
+ dst_s.acknowledge((Message)dst_q.dequeue());
+ assertNotNull(src_rr.getReply(60));
+
+ assertFalse(src_s.send(new SimpleMessage("1"), "test").isAccepted());
+ dst_s.acknowledge((Message)dst_q.dequeue());
+ assertNotNull(src_rr.getReply(60));
+
+ assertTrue(src_s.send(new SimpleMessage("12"), "test").isAccepted());
+ assertTrue(dst_q.waitSize(1, 60));
+ assertFalse(src_s.send(new SimpleMessage("1"), "test").isAccepted());
+ dst_s.acknowledge((Message)dst_q.dequeue());
+ assertNotNull(src_rr.getReply(60));
+
+ // Close sessions.
+ src_s.close();
+ dst_s.destroy();
+ }
+
+ public void testDynamicWindowSize() {
+ CustomTimer timer = new CustomTimer();
+ DynamicThrottlePolicy policy = new DynamicThrottlePolicy(timer);
+
+ policy.setWindowSizeIncrement(5);
+ policy.setResizeRate(1);
+
+ double windowSize = getWindowSize(policy, timer, 100);
+ assertTrue(windowSize >= 90 && windowSize <= 110);
+
+ windowSize = getWindowSize(policy, timer, 200);
+ assertTrue(windowSize >= 90 && windowSize <= 210);
+
+ windowSize = getWindowSize(policy, timer, 50);
+ assertTrue(windowSize >= 9 && windowSize <= 55);
+
+ windowSize = getWindowSize(policy, timer, 500);
+ assertTrue(windowSize >= 90 && windowSize <= 505);
+
+ windowSize = getWindowSize(policy, timer, 100);
+ assertTrue(windowSize >= 90 && windowSize <= 115);
+ }
+
+ public void testIdleTimePeriod() {
+ CustomTimer timer = new CustomTimer();
+ DynamicThrottlePolicy policy = new DynamicThrottlePolicy(timer);
+
+ policy.setWindowSizeIncrement(5);
+ policy.setResizeRate(1);
+
+ double windowSize = getWindowSize(policy, timer, 100);
+ assertTrue(windowSize >= 90 && windowSize <= 110);
+
+ Message msg = new SimpleMessage("foo");
+ timer.millis += 30 * 1000;
+ assertTrue(policy.canSend(msg, 0));
+ assertTrue(windowSize >= 90 && windowSize <= 110);
+
+ timer.millis += 60 * 1000 + 1;
+ assertTrue(policy.canSend(msg, 50));
+ assertEquals(55, policy.getMaxPendingCount());
+
+ timer.millis += 60 * 1000 + 1;
+ assertTrue(policy.canSend(msg, 0));
+ assertEquals(5, policy.getMaxPendingCount());
+
+ }
+
+ public void testMinWindowSize() {
+ CustomTimer timer = new CustomTimer();
+ DynamicThrottlePolicy policy = new DynamicThrottlePolicy(timer);
+
+ policy.setWindowSizeIncrement(5);
+ policy.setResizeRate(1);
+ policy.setMinWindowSize(150);
+
+ double windowSize = getWindowSize(policy, timer, 200);
+ assertTrue(windowSize >= 150 && windowSize <= 210);
+ }
+
+ public void testMaxWindowSize() {
+ CustomTimer timer = new CustomTimer();
+ DynamicThrottlePolicy policy = new DynamicThrottlePolicy(timer);
+
+ policy.setWindowSizeIncrement(5);
+ policy.setResizeRate(1);
+ policy.setMaxWindowSize(50);
+
+ double windowSize = getWindowSize(policy, timer, 100);
+ assertTrue(windowSize >= 40 && windowSize <= 50);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Utilities
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ private int getWindowSize(DynamicThrottlePolicy policy, CustomTimer timer, int maxPending) {
+ Message msg = new SimpleMessage("foo");
+ Reply reply = new SimpleReply("bar");
+ reply.setContext(1);
+ for (int i = 0; i < 999; ++i) {
+ int numPending = 0;
+ while (policy.canSend(msg, numPending)) {
+ policy.processMessage(msg);
+ ++numPending;
+ }
+
+ long tripTime = (numPending < maxPending) ? 1000 : 1000 + (numPending - maxPending) * 1000;
+ timer.millis += tripTime;
+
+ while (--numPending >= 0) {
+ policy.processReply(reply);
+ }
+ }
+ int ret = policy.getMaxPendingCount();
+ System.out.println("getWindowSize() = " + ret);
+ return ret;
+ }
+
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/TimeoutTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/TimeoutTestCase.java
new file mode 100755
index 00000000000..05a14bf8d16
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/TimeoutTestCase.java
@@ -0,0 +1,102 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.UnknownHostException;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TimeoutTestCase {
+
+ private final Slobrok slobrok;
+ private final TestServer srcServer, dstServer;
+ private final SourceSession srcSession;
+ private final DestinationSession dstSession;
+
+ public TimeoutTestCase() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst"))
+ .setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ dstSession = dstServer.mb.createDestinationSession(new DestinationSessionParams()
+ .setName("session")
+ .setMessageHandler(new Receptor()));
+ srcServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(new Receptor()));
+ }
+
+ @Before
+ public void waitForSlobrokRegistration() {
+ assertTrue(srcServer.waitSlobrok("dst/session", 1));
+ }
+
+ @After
+ public void destroyResources() {
+ slobrok.stop();
+ dstSession.destroy();
+ dstServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(0);
+ if (msg != null) {
+ msg.discard();
+ }
+ }
+
+ @Test
+ public void requireThatMessageCanTimeout() throws ListenFailedException, UnknownHostException {
+ srcSession.setTimeout(1);
+ assertSend(srcSession, newMessage(), "dst/session");
+ assertTimeout(((Receptor)srcSession.getReplyHandler()).getReply(60));
+ }
+
+ @Test
+ public void requireThatZeroTimeoutMeansImmediateTimeout() throws ListenFailedException, UnknownHostException {
+ srcSession.setTimeout(0);
+ assertSend(srcSession, newMessage(), "dst/session");
+ assertTimeout(((Receptor)srcSession.getReplyHandler()).getReply(60));
+ }
+
+ private static void assertSend(SourceSession session, Message msg, String route) {
+ assertTrue(session.send(msg, Route.parse(route)).isAccepted());
+ }
+
+ private static void assertTimeout(Reply reply) {
+ assertNotNull(reply);
+ assertTrue(reply.getTrace().toString(), hasError(reply, ErrorCode.TIMEOUT));
+ }
+
+ private static Message newMessage() {
+ Message msg = new SimpleMessage("msg");
+ msg.getTrace().setLevel(9);
+ return msg;
+ }
+
+ private static boolean hasError(Reply reply, int errorCode) {
+ for (int i = 0; i < reply.getNumErrors(); ++i) {
+ if (reply.getError(i).getCode() == errorCode) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/TraceTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/TraceTestCase.java
new file mode 100755
index 00000000000..c6b67087b7f
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/TraceTestCase.java
@@ -0,0 +1,286 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TraceTestCase extends junit.framework.TestCase {
+
+ public void testEncodeDecode() {
+ assertEquals("()", TraceNode.decode("").encode());
+ assertEquals("()", TraceNode.decode("[xyz").encode());
+ assertEquals("([xyz][])", TraceNode.decode("[xyz][]").encode());
+ assertEquals("[xyz]", TraceNode.decode("[xyz]").encode());
+ assertEquals("()", TraceNode.decode("{()").encode());
+ assertEquals("({()}{})", TraceNode.decode("{()}{}").encode());
+ assertEquals("{()}", TraceNode.decode("{()}").encode());
+ assertEquals("()", TraceNode.decode("({}").encode());
+ assertEquals("(({})())", TraceNode.decode("({})()").encode());
+ assertEquals("([])", TraceNode.decode("([])").encode());
+
+ assertTrue(TraceNode.decode("").isEmpty());
+ assertTrue(!TraceNode.decode("([note])").isEmpty());
+
+ String str =
+ "([[17/Jun/2009:09:02:30 +0200\\] Message (type 1) received at 'dst' for session 'session'.]" +
+ "[[17/Jun/2009:09:02:30 +0200\\] [APP_TRANSIENT_ERROR @ localhost\\]: err1]" +
+ "[[17/Jun/2009:09:02:30 +0200\\] Sending reply (version 4.2) from 'dst'.])";
+ System.out.println(TraceNode.decode(str).toString());
+ assertEquals(str, TraceNode.decode(str).encode());
+
+ str = "([Note 0][Note 1]{[Note 2]}{([Note 3])({[Note 4]})})";
+ TraceNode t = TraceNode.decode(str);
+ assertEquals(str, t.encode());
+
+ assertTrue(t.isRoot());
+ assertTrue(t.isStrict());
+ assertTrue(!t.isLeaf());
+ assertEquals(4, t.getNumChildren());
+
+ {
+ TraceNode c = t.getChild(0);
+ assertTrue(c.isLeaf());
+ assertEquals("Note 0", c.getNote());
+ }
+ {
+ TraceNode c = t.getChild(1);
+ assertTrue(c.isLeaf());
+ assertEquals("Note 1", c.getNote());
+ }
+ {
+ TraceNode c = t.getChild(2);
+ assertTrue(!c.isLeaf());
+ assertTrue(!c.isStrict());
+ assertEquals(1, c.getNumChildren());
+ {
+ TraceNode d = c.getChild(0);
+ assertTrue(d.isLeaf());
+ assertEquals("Note 2", d.getNote());
+ }
+ }
+ {
+ TraceNode c = t.getChild(3);
+ assertTrue(!c.isStrict());
+ assertEquals(2, c.getNumChildren());
+ {
+ TraceNode d = c.getChild(0);
+ assertTrue(d.isStrict());
+ assertTrue(!d.isLeaf());
+ assertEquals(1, d.getNumChildren());
+ {
+ TraceNode e = d.getChild(0);
+ assertTrue(e.isLeaf());
+ assertEquals("Note 3", e.getNote());
+ }
+ }
+ {
+ TraceNode d = c.getChild(1);
+ assertTrue(d.isStrict());
+ assertEquals(1, d.getNumChildren());
+ {
+ TraceNode e = d.getChild(0);
+ assertTrue(!e.isStrict());
+ assertEquals(1, e.getNumChildren());
+ {
+ TraceNode f = e.getChild(0);
+ assertTrue(f.isLeaf());
+ assertEquals("Note 4", f.getNote());
+ }
+ }
+ }
+ }
+ }
+
+ public void testReservedChars() {
+ TraceNode t = new TraceNode();
+ t.addChild("abc(){}[]\\xyz");
+ assertEquals("abc(){}[]\\xyz", t.getChild(0).getNote());
+ assertEquals("([abc(){}[\\]\\\\xyz])", t.encode());
+ {
+ // test swap/clear/empty here
+ TraceNode t2 = new TraceNode();
+ assertTrue(t2.isEmpty());
+ t2.swap(t);
+ assertTrue(!t2.isEmpty());
+ assertEquals("abc(){}[]\\xyz", t2.getChild(0).getNote());
+ assertEquals("([abc(){}[\\]\\\\xyz])", t2.encode());
+ t2.clear();
+ assertTrue(t2.isEmpty());
+ }
+ }
+
+ public void testAdd() {
+ TraceNode t1 = TraceNode.decode("([x])");
+ TraceNode t2 = TraceNode.decode("([y])");
+ TraceNode t3 = TraceNode.decode("([z])");
+
+ t1.addChild(t2);
+ assertEquals("([x]([y]))", t1.encode());
+ assertTrue(t1.getChild(1).isStrict());
+ t1.addChild("txt");
+ assertTrue(t1.getChild(2).isLeaf());
+ assertEquals("([x]([y])[txt])", t1.encode());
+ t3.addChild(t1);
+ assertEquals("([z]([x]([y])[txt]))", t3.encode());
+
+ // crazy but possible (everything is by value)
+ t2.addChild(t2).addChild(t2);
+ assertEquals("([y]([y])([y]([y])))", t2.encode());
+ }
+
+ public void testStrict() {
+ assertEquals("{}", TraceNode.decode("()").setStrict(false).encode());
+ assertEquals("{[x]}", TraceNode.decode("([x])").setStrict(false).encode());
+ assertEquals("{[x][y]}", TraceNode.decode("([x][y])").setStrict(false).encode());
+ }
+
+ public void testTraceLevel() {
+ Trace t = new Trace();
+ t.setLevel(4);
+ assertEquals(4, t.getLevel());
+ t.trace(9, "no");
+ assertEquals(0, t.getRoot().getNumChildren());
+ t.trace(8, "no");
+ assertEquals(0, t.getRoot().getNumChildren());
+ t.trace(7, "no");
+ assertEquals(0, t.getRoot().getNumChildren());
+ t.trace(6, "no");
+ assertEquals(0, t.getRoot().getNumChildren());
+ t.trace(5, "no");
+ assertEquals(0, t.getRoot().getNumChildren());
+ t.trace(4, "yes");
+ assertEquals(1, t.getRoot().getNumChildren());
+ t.trace(3, "yes");
+ assertEquals(2, t.getRoot().getNumChildren());
+ t.trace(2, "yes");
+ assertEquals(3, t.getRoot().getNumChildren());
+ t.trace(1, "yes");
+ assertEquals(4, t.getRoot().getNumChildren());
+ t.trace(0, "yes");
+ assertEquals(5, t.getRoot().getNumChildren());
+ }
+
+ public void testCompact() {
+ assertEquals("()", TraceNode.decode("()").compact().encode());
+ assertEquals("()", TraceNode.decode("(())").compact().encode());
+ assertEquals("()", TraceNode.decode("(()())").compact().encode());
+ assertEquals("()", TraceNode.decode("({})").compact().encode());
+ assertEquals("()", TraceNode.decode("({}{})").compact().encode());
+ assertEquals("()", TraceNode.decode("({{}{}})").compact().encode());
+
+ assertEquals("([x])", TraceNode.decode("([x])").compact().encode());
+ assertEquals("([x])", TraceNode.decode("(([x]))").compact().encode());
+ assertEquals("([x][y])", TraceNode.decode("(([x])([y]))").compact().encode());
+ assertEquals("([x])", TraceNode.decode("({[x]})").compact().encode());
+ assertEquals("([x][y])", TraceNode.decode("({[x]}{[y]})").compact().encode());
+ assertEquals("({[x][y]})", TraceNode.decode("({{[x]}{[y]}})").compact().encode());
+
+ assertEquals("([a][b][c][d])", TraceNode.decode("(([a][b])([c][d]))").compact().encode());
+ assertEquals("({[a][b]}{[c][d]})", TraceNode.decode("({[a][b]}{[c][d]})").compact().encode());
+ assertEquals("({[a][b][c][d]})", TraceNode.decode("({{[a][b]}{[c][d]}})").compact().encode());
+ assertEquals("({([a][b])([c][d])})", TraceNode.decode("({([a][b])([c][d])})").compact().encode());
+
+ assertEquals("({{}{(({()}({}){()(){}}){})}})", TraceNode.decode("({{}{(({()}({}){()(){}}){})}})").encode());
+ assertEquals("()", TraceNode.decode("({{}{(({()}({}){()(){}}){})}})").compact().encode());
+ assertEquals("([x])", TraceNode.decode("({{}{([x]({()}({}){()(){}}){})}})").compact().encode());
+ assertEquals("([x])", TraceNode.decode("({{}{(({()}({[x]}){()(){}}){})}})").compact().encode());
+ assertEquals("([x])", TraceNode.decode("({{}{(({()}({}){()(){}})[x]{})}})").compact().encode());
+
+ assertEquals("({[a][b][c][d][e][f]})", TraceNode.decode("({({[a][b]})({[c][d]})({[e][f]})})").compact().encode());
+ }
+
+ public void testSort() {
+ assertEquals("([b][a][c])", TraceNode.decode("([b][a][c])").sort().encode());
+ assertEquals("({[a][b][c]})", TraceNode.decode("({[b][a][c]})").sort().encode());
+ assertEquals("(([c][a])([b]))", TraceNode.decode("(([c][a])([b]))").sort().encode());
+ assertEquals("({[b]([c][a])})", TraceNode.decode("({([c][a])[b]})").sort().encode());
+ assertEquals("({[a][c]}[b])", TraceNode.decode("({[c][a]}[b])").sort().encode());
+ assertEquals("({([b]){[a][c]}})", TraceNode.decode("({{[c][a]}([b])})").sort().encode());
+ }
+
+ public void testNormalize() {
+ TraceNode t1 = TraceNode.decode("({([a][b]{[x][y]([p][q])})([c][d])([e][f])})");
+ TraceNode t2 = TraceNode.decode("({([a][b]{[y][x]([p][q])})([c][d])([e][f])})");
+ TraceNode t3 = TraceNode.decode("({([a][b]{[y]([p][q])[x]})([c][d])([e][f])})");
+ TraceNode t4 = TraceNode.decode("({([e][f])([a][b]{[y]([p][q])[x]})([c][d])})");
+ TraceNode t5 = TraceNode.decode("({([e][f])([c][d])([a][b]{([p][q])[y][x]})})");
+
+ TraceNode tx = TraceNode.decode("({([b][a]{[x][y]([p][q])})([c][d])([e][f])})");
+ TraceNode ty = TraceNode.decode("({([a][b]{[x][y]([p][q])})([d][c])([e][f])})");
+ TraceNode tz = TraceNode.decode("({([a][b]{[x][y]([q][p])})([c][d])([e][f])})");
+
+ assertEquals("({([a][b]{[x][y]([p][q])})([c][d])([e][f])})", t1.compact().encode());
+
+ assertTrue(!t1.compact().encode().equals(t2.compact().encode()));
+ assertTrue(!t1.compact().encode().equals(t3.compact().encode()));
+ assertTrue(!t1.compact().encode().equals(t4.compact().encode()));
+ assertTrue(!t1.compact().encode().equals(t5.compact().encode()));
+ assertTrue(!t1.compact().encode().equals(tx.compact().encode()));
+ assertTrue(!t1.compact().encode().equals(ty.compact().encode()));
+ assertTrue(!t1.compact().encode().equals(tz.compact().encode()));
+
+ System.out.println("1: " + t1.normalize().encode());
+ System.out.println("2: " + t2.normalize().encode());
+ System.out.println("3: " + t3.normalize().encode());
+ System.out.println("4: " + t4.normalize().encode());
+ System.out.println("5: " + t5.normalize().encode());
+ System.out.println("x: " + tx.normalize().encode());
+ System.out.println("y: " + ty.normalize().encode());
+ System.out.println("z: " + tz.normalize().encode());
+ assertTrue(t1.normalize().encode().equals(t2.normalize().encode()));
+ assertTrue(t1.normalize().encode().equals(t3.normalize().encode()));
+ assertTrue(t1.normalize().encode().equals(t4.normalize().encode()));
+ assertTrue(t1.normalize().encode().equals(t5.normalize().encode()));
+ assertTrue(!t1.normalize().encode().equals(tx.normalize().encode()));
+ assertTrue(!t1.normalize().encode().equals(ty.normalize().encode()));
+ assertTrue(!t1.normalize().encode().equals(tz.normalize().encode()));
+
+ assertEquals("({([c][d])([e][f])([a][b]{[x][y]([p][q])})})", t1.normalize().encode());
+ }
+
+ public void testTraceDump() {
+ {
+ Trace big = new Trace();
+ TraceNode b1 = new TraceNode();
+ TraceNode b2 = new TraceNode();
+ for (int i = 0; i < 100; ++i) {
+ b2.addChild("test");
+ }
+ for (int i = 0; i < 10; ++i) {
+ b1.addChild(b2);
+ }
+ for (int i = 0; i < 10; ++i) {
+ big.getRoot().addChild(b1);
+ }
+ String normal = big.toString();
+ String full = big.getRoot().toString();
+ assertTrue(normal.length() > 30000);
+ assertTrue(normal.length() < 32000);
+ assertTrue(full.length() > 50000);
+ assertEquals(normal.substring(0, 30000), full.substring(0, 30000));
+ }
+ {
+ TraceNode s1 = new TraceNode();
+ TraceNode s2 = new TraceNode();
+ s2.addChild("test");
+ s2.addChild("test");
+ s1.addChild(s2);
+ s1.addChild(s2);
+ assertEquals("...\n", s1.toString(0));
+ assertEquals("<trace>\n...\n", s1.toString(1));
+ assertEquals("<trace>\n" + // 8 8
+ " <trace>\n" + // 12 20
+ " test\n" + // 13 33
+ "...\n", s1.toString(33));
+ assertEquals("<trace>\n" + // 8 8
+ " test\n" + // 9 17
+ " test\n" + // 9 26
+ "...\n", s2.toString(26));
+ assertEquals("<trace>\n" + // 8 8
+ " test\n" + // 9 17
+ " test\n" + // 9 26
+ "</trace>\n", s2.toString(27));
+ assertEquals(s2.toString(27), s2.toString());
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/TraceTripTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/TraceTripTestCase.java
new file mode 100755
index 00000000000..e675a8ef98a
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/TraceTripTestCase.java
@@ -0,0 +1,116 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.RoutingTableSpec;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TraceTripTestCase extends junit.framework.TestCase {
+
+ Slobrok slobrok;
+ TestServer src;
+ TestServer pxy;
+ TestServer dst;
+
+ public TraceTripTestCase(String message) {
+ super(message);
+ }
+
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ RoutingTableSpec table = new RoutingTableSpec(SimpleProtocol.NAME)
+ .addHop("pxy", "test/pxy/session", Arrays.asList("test/pxy/session"))
+ .addHop("dst", "test/dst/session", Arrays.asList("test/dst/session"))
+ .addRoute("test", Arrays.asList("pxy", "dst"));
+
+ slobrok = new Slobrok();
+ src = new TestServer("test/src", table, slobrok, null, null);
+ pxy = new TestServer("test/pxy", table, slobrok, null, null);
+ dst = new TestServer("test/dst", table, slobrok, null, null);
+ }
+
+ public void tearDown() {
+ dst.destroy();
+ pxy.destroy();
+ src.destroy();
+ slobrok.stop();
+ }
+
+ public void testTrip() {
+ Receptor src_rr = new Receptor();
+ SourceSession src_s = src.mb.createSourceSession(src_rr);
+
+ new Proxy(pxy.mb);
+ assertTrue(src.waitSlobrok("test/pxy/session", 1));
+
+ new Server(dst.mb);
+ assertTrue(src.waitSlobrok("test/dst/session", 1));
+ assertTrue(pxy.waitSlobrok("test/dst/session", 1));
+
+ Message msg = new SimpleMessage("");
+ msg.getTrace().setLevel(1);
+ msg.getTrace().trace(1, "Client message", false);
+ src_s.send(msg, "test");
+ Reply reply = src_rr.getReply(60);
+ reply.getTrace().trace(1, "Client reply", false);
+ assertTrue(reply.getNumErrors() == 0);
+
+ TraceNode t = new TraceNode()
+ .addChild("Client message")
+ .addChild("Proxy message")
+ .addChild("Server message")
+ .addChild("Server reply")
+ .addChild("Proxy reply")
+ .addChild("Client reply");
+ System.out.println("reply: " + reply.getTrace().getRoot().encode());
+ System.out.println("want : " + t.encode());
+ assertTrue(reply.getTrace().getRoot().encode().equals(t.encode()));
+ }
+
+ private static class Proxy implements MessageHandler, ReplyHandler {
+ private IntermediateSession session;
+
+ public Proxy(MessageBus bus) {
+ session = bus.createIntermediateSession("session", true, this, this);
+ }
+
+ public void handleMessage(Message msg) {
+ msg.getTrace().trace(1, "Proxy message", false);
+ System.out.println(msg.getTrace().getRoot().encode());
+ session.forward(msg);
+ }
+
+ public void handleReply(Reply reply) {
+ reply.getTrace().trace(1, "Proxy reply", false);
+ System.out.println(reply.getTrace().getRoot().encode());
+ session.forward(reply);
+ }
+ }
+
+ private static class Server implements MessageHandler {
+ private DestinationSession session;
+
+ public Server(MessageBus bus) {
+ session = bus.createDestinationSession("session", true, this);
+ }
+
+ public void handleMessage(Message msg) {
+ msg.getTrace().trace(1, "Server message", false);
+ System.out.println(msg.getTrace().getRoot().encode());
+ Reply reply = new EmptyReply();
+ msg.swapState(reply);
+ reply.getTrace().trace(1, "Server reply", false);
+ System.out.println(reply.getTrace().getRoot().encode());
+ session.reply(reply);
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/IdentityTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/IdentityTestCase.java
new file mode 100644
index 00000000000..2191b2750bf
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/IdentityTestCase.java
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class IdentityTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Identity id = new Identity("foo");
+ assertNotNull(id.getHostname());
+ assertEquals("foo", id.getServicePrefix());
+ }
+
+ @Test
+ public void requireThatCopyConstructorWorks() {
+ Identity lhs = new Identity("foo");
+ Identity rhs = new Identity(lhs);
+ assertEquals(lhs.getHostname(), rhs.getHostname());
+ assertEquals(lhs.getServicePrefix(), rhs.getServicePrefix());
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/local/LocalNetworkTest.java b/messagebus/src/test/java/com/yahoo/messagebus/network/local/LocalNetworkTest.java
new file mode 100644
index 00000000000..296e724a502
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/local/LocalNetworkTest.java
@@ -0,0 +1,132 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.local;
+
+import com.yahoo.messagebus.DestinationSession;
+import com.yahoo.messagebus.DestinationSessionParams;
+import com.yahoo.messagebus.EmptyReply;
+import com.yahoo.messagebus.IntermediateSession;
+import com.yahoo.messagebus.IntermediateSessionParams;
+import com.yahoo.messagebus.Message;
+import com.yahoo.messagebus.MessageBus;
+import com.yahoo.messagebus.MessageBusParams;
+import com.yahoo.messagebus.MessageHandler;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.messagebus.ReplyHandler;
+import com.yahoo.messagebus.SourceSession;
+import com.yahoo.messagebus.SourceSessionParams;
+import com.yahoo.messagebus.routing.Hop;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import com.yahoo.messagebus.test.SimpleReply;
+import org.junit.Test;
+
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class LocalNetworkTest {
+
+ @Test
+ public void requireThatLocalNetworkCanSendAndReceive() throws InterruptedException {
+ final LocalWire wire = new LocalWire();
+
+ final Server serverA = new Server(wire);
+ final SourceSession source = serverA.newSourceSession();
+
+ final Server serverB = new Server(wire);
+ final IntermediateSession intermediate = serverB.newIntermediateSession();
+
+ final Server serverC = new Server(wire);
+ final DestinationSession destination = serverC.newDestinationSession();
+
+ Message msg = new SimpleMessage("foo");
+ msg.setRoute(new Route().addHop(Hop.parse(intermediate.getConnectionSpec()))
+ .addHop(Hop.parse(destination.getConnectionSpec())));
+ assertThat(source.send(msg).isAccepted(), is(true));
+
+ msg = serverB.messages.poll(60, TimeUnit.SECONDS);
+ assertThat(msg, instanceOf(SimpleMessage.class));
+ assertThat(((SimpleMessage)msg).getValue(), is("foo"));
+ intermediate.forward(msg);
+
+ msg = serverC.messages.poll(60, TimeUnit.SECONDS);
+ assertThat(msg, instanceOf(SimpleMessage.class));
+ assertThat(((SimpleMessage)msg).getValue(), is("foo"));
+ Reply reply = new SimpleReply("bar");
+ reply.swapState(msg);
+ destination.reply(reply);
+
+ reply = serverB.replies.poll(60, TimeUnit.SECONDS);
+ assertThat(reply, instanceOf(SimpleReply.class));
+ assertThat(((SimpleReply)reply).getValue(), is("bar"));
+ intermediate.forward(reply);
+
+ reply = serverA.replies.poll(60, TimeUnit.SECONDS);
+ assertThat(reply, instanceOf(SimpleReply.class));
+ assertThat(((SimpleReply)reply).getValue(), is("bar"));
+
+ serverA.mbus.destroy();
+ serverB.mbus.destroy();
+ serverC.mbus.destroy();
+ }
+
+ @Test
+ public void requireThatUnknownServiceRepliesWithNoAddressForService() throws InterruptedException {
+ final Server server = new Server(new LocalWire());
+ final SourceSession source = server.newSourceSession();
+
+ final Message msg = new SimpleMessage("foo").setRoute(Route.parse("bar"));
+ assertThat(source.send(msg).isAccepted(), is(true));
+ final Reply reply = server.replies.poll(60, TimeUnit.SECONDS);
+ assertThat(reply, instanceOf(EmptyReply.class));
+
+ server.mbus.destroy();
+ }
+
+ private static class Server implements MessageHandler, ReplyHandler {
+
+ final MessageBus mbus;
+ final BlockingDeque<Message> messages = new LinkedBlockingDeque<>();
+ final BlockingDeque<Reply> replies = new LinkedBlockingDeque<>();
+
+ Server(final LocalWire wire) {
+ mbus = new MessageBus(new LocalNetwork(wire),
+ new MessageBusParams().addProtocol(new SimpleProtocol())
+ .setRetryPolicy(null));
+ }
+
+ SourceSession newSourceSession() {
+ return mbus.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(this));
+ }
+
+ IntermediateSession newIntermediateSession() {
+ return mbus.createIntermediateSession(new IntermediateSessionParams()
+ .setMessageHandler(this)
+ .setReplyHandler(this));
+ }
+
+ DestinationSession newDestinationSession() {
+ return mbus.createDestinationSession(new DestinationSessionParams()
+ .setMessageHandler(this));
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ messages.addLast(msg);
+ }
+
+ @Override
+ public void handleReply(final Reply reply) {
+ replies.addLast(reply);
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/BasicNetworkTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/BasicNetworkTestCase.java
new file mode 100644
index 00000000000..7fe481a2196
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/BasicNetworkTestCase.java
@@ -0,0 +1,152 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+
+import com.yahoo.concurrent.SystemTimer;
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingTableSpec;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import com.yahoo.messagebus.test.SimpleReply;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class BasicNetworkTestCase extends junit.framework.TestCase {
+
+ Slobrok slobrok;
+ TestServer src;
+ TestServer pxy;
+ TestServer dst;
+
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ RoutingTableSpec table = new RoutingTableSpec(SimpleProtocol.NAME);
+ table.addHop("pxy", "test/pxy/session", Arrays.asList("test/pxy/session"));
+ table.addHop("dst", "test/dst/session", Arrays.asList("test/dst/session"));
+ table.addRoute("test", Arrays.asList("pxy", "dst"));
+ slobrok = new Slobrok();
+ src = new TestServer("test/src", table, slobrok, null, null);
+ pxy = new TestServer("test/pxy", table, slobrok, null, null);
+ dst = new TestServer("test/dst", table, slobrok, null, null);
+ }
+
+ public void tearDown() {
+ dst.destroy();
+ pxy.destroy();
+ src.destroy();
+ slobrok.stop();
+ }
+
+ public void testNetwork() {
+ // set up receptors
+ Receptor src_rr = new Receptor();
+ Receptor pxy_mr = new Receptor();
+ Receptor pxy_rr = new Receptor();
+ Receptor dst_mr = new Receptor();
+
+ // set up sessions
+ SourceSessionParams sp = new SourceSessionParams();
+ sp.setTimeout(30.0);
+
+ SourceSession ss = src.mb.createSourceSession(src_rr, sp);
+ IntermediateSession is = pxy.mb.createIntermediateSession("session", true, pxy_mr, pxy_rr);
+ DestinationSession ds = dst.mb.createDestinationSession("session", true, dst_mr);
+
+ // wait for slobrok registration
+ assertTrue(src.waitSlobrok("test/pxy/session", 1));
+ assertTrue(src.waitSlobrok("test/dst/session", 1));
+ assertTrue(pxy.waitSlobrok("test/dst/session", 1));
+
+ // send message on client
+ ss.send(new SimpleMessage("test message"), "test");
+
+ // check message on proxy
+ Message msg = pxy_mr.getMessage(60);
+ assertTrue(msg != null);
+ assertEquals(SimpleProtocol.MESSAGE, msg.getType());
+ SimpleMessage sm = (SimpleMessage) msg;
+ assertEquals("test message", sm.getValue());
+
+ // forward message on proxy
+ sm.setValue(sm.getValue() + " pxy");
+ is.forward(sm);
+
+ // check message on server
+ msg = dst_mr.getMessage(60);
+ assertTrue(msg != null);
+ assertEquals(SimpleProtocol.MESSAGE, msg.getType());
+ sm = (SimpleMessage) msg;
+ assertEquals("test message pxy", sm.getValue());
+
+ // send reply on server
+ SimpleReply sr = new SimpleReply("test reply");
+ sm.swapState(sr);
+ ds.reply(sr);
+
+ // check reply on proxy
+ Reply reply = pxy_rr.getReply(60);
+ assertTrue(reply != null);
+ assertEquals(SimpleProtocol.REPLY, reply.getType());
+ sr = (SimpleReply) reply;
+ assertEquals("test reply", sr.getValue());
+
+ // forward reply on proxy
+ sr.setValue(sr.getValue() + " pxy");
+ is.forward(sr);
+
+ // check reply on client
+ reply = src_rr.getReply(60);
+ assertTrue(reply != null);
+ assertEquals(SimpleProtocol.REPLY, reply.getType());
+ sr = (SimpleReply) reply;
+ assertEquals("test reply pxy", sr.getValue());
+
+ ss.destroy();
+ is.destroy();
+ ds.destroy();
+ }
+
+ public void testTimeoutsFollowMessage() {
+ SourceSessionParams params = new SourceSessionParams().setTimeout(600.0);
+ SourceSession ss = src.mb.createSourceSession(new Receptor(), params);
+ DestinationSession ds = dst.mb.createDestinationSession("session", true, new Receptor());
+ assertTrue(src.waitSlobrok("test/dst/session", 1));
+
+ // Test default timeouts being set.
+ Message msg = new SimpleMessage("msg");
+ msg.getTrace().setLevel(9);
+ long now = SystemTimer.INSTANCE.milliTime();
+ assertTrue(ss.send(msg, Route.parse("dst")).isAccepted());
+
+ assertNotNull(msg = ((Receptor)ds.getMessageHandler()).getMessage(60));
+ assertTrue(msg.getTimeReceived() >= now);
+ assertTrue(params.getTimeout() * 1000 >= msg.getTimeRemaining());
+ ds.acknowledge(msg);
+
+ assertNotNull(((Receptor)ss.getReplyHandler()).getReply(60));
+
+ // Test default timeouts being overwritten.
+ msg = new SimpleMessage("msg");
+ msg.getTrace().setLevel(9);
+ msg.setTimeRemaining(2 * (long)(params.getTimeout() * 1000));
+ assertTrue(ss.send(msg, Route.parse("dst")).isAccepted());
+
+ assertNotNull(msg = ((Receptor)ds.getMessageHandler()).getMessage(60));
+ assertTrue(params.getTimeout() * 1000 < msg.getTimeRemaining());
+ ds.acknowledge(msg);
+
+ assertNotNull(((Receptor)ss.getReplyHandler()).getReply(60));
+
+ ss.destroy();
+ ds.destroy();
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/LoadBalanceTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/LoadBalanceTestCase.java
new file mode 100644
index 00000000000..3f6c449679e
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/LoadBalanceTestCase.java
@@ -0,0 +1,96 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.QueueAdapter;
+import com.yahoo.messagebus.test.SimpleMessage;
+
+import java.net.UnknownHostException;
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class LoadBalanceTestCase extends junit.framework.TestCase {
+
+ public void testLoadBalance() throws ListenFailedException, UnknownHostException {
+ Slobrok slobrok = new Slobrok();
+ TestServer src = new TestServer("src", null, slobrok, null, null);
+ TestServer dst1 = new TestServer("dst/1", null, slobrok, null, null);
+ TestServer dst2 = new TestServer("dst/2", null, slobrok, null, null);
+ TestServer dst3 = new TestServer("dst/3", null, slobrok, null, null);
+
+ // set up handlers
+ final QueueAdapter sq = new QueueAdapter();
+ SourceSession ss = src.mb.createSourceSession(new SourceSessionParams().setTimeout(600.0).setThrottlePolicy(null)
+ .setReplyHandler(new ReplyHandler() {
+ @Override
+ public void handleReply(Reply reply) {
+ System.out.println(Thread.currentThread().getName() + ": Reply '" +
+ ((SimpleMessage)reply.getMessage()).getValue() + "' received at source.");
+ sq.handleReply(reply);
+ }
+ }));
+ SimpleDestination h1 = new SimpleDestination(dst1.mb, dst1.net.getIdentity());
+ SimpleDestination h2 = new SimpleDestination(dst2.mb, dst2.net.getIdentity());
+ SimpleDestination h3 = new SimpleDestination(dst3.mb, dst3.net.getIdentity());
+ assertTrue(src.waitSlobrok("dst/*/session", 3));
+
+ // send messages
+ int msgCnt = 30; // should be divisible by 3
+ for (int i = 0; i < msgCnt; ++i) {
+ ss.send(new SimpleMessage("msg" + i), Route.parse("dst/*/session"));
+ }
+
+ // wait for replies
+ assertTrue(sq.waitSize(msgCnt, 60));
+
+ // check handler message distribution
+ assertEquals(msgCnt / 3, h1.getCount());
+ assertEquals(msgCnt / 3, h2.getCount());
+ assertEquals(msgCnt / 3, h3.getCount());
+
+ ss.destroy();
+ h1.session.destroy();
+ h2.session.destroy();
+ h3.session.destroy();
+
+ dst3.destroy();
+ dst2.destroy();
+ dst1.destroy();
+ src.destroy();
+ slobrok.stop();
+ }
+
+ /**
+ * Implements a simple destination that counts and acknowledges all messages received.
+ */
+ private static class SimpleDestination implements MessageHandler {
+
+ final DestinationSession session;
+ final String ident;
+ int cnt = 0;
+
+ SimpleDestination(MessageBus mb, Identity ident) {
+ this.session = mb.createDestinationSession("session", true, this);
+ this.ident = ident.getServicePrefix();
+ }
+
+ @Override
+ public synchronized void handleMessage(Message msg) {
+ System.out.println(
+ Thread.currentThread().getName() + ": " +
+ "Message '" + ((SimpleMessage)msg).getValue() + "' received at '" + ident + "'.");
+ session.acknowledge(msg);
+ ++cnt;
+ }
+
+ public synchronized int getCount() {
+ return cnt;
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/OOSTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/OOSTestCase.java
new file mode 100755
index 00000000000..e65621e6a10
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/OOSTestCase.java
@@ -0,0 +1,200 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.test.OOSServer;
+import com.yahoo.messagebus.network.rpc.test.OOSState;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+
+import java.net.UnknownHostException;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OOSTestCase extends junit.framework.TestCase {
+
+ private static class MyServer extends TestServer implements MessageHandler {
+ DestinationSession session;
+
+ public MyServer(String name, Slobrok slobrok, String oosServerPattern)
+ throws ListenFailedException, UnknownHostException
+ {
+ super(new MessageBusParams().setRetryPolicy(null).addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams()
+ .setIdentity(new Identity(name))
+ .setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok))
+ .setOOSServerPattern(oosServerPattern));
+ session = mb.createDestinationSession("session", true, this);
+ }
+
+ public boolean destroy() {
+ session.destroy();
+ return super.destroy();
+ }
+
+ public void handleMessage(Message msg) {
+ session.acknowledge(msg);
+ }
+ }
+
+ private static void assertError(SourceSession src, String dst, int error) {
+ Message msg = new SimpleMessage("msg");
+ msg.getTrace().setLevel(9);
+ assertTrue(src.send(msg, Route.parse(dst)).isAccepted());
+ Reply reply = ((Receptor) src.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ if (error == ErrorCode.NONE) {
+ assertFalse(reply.hasErrors());
+ } else {
+ assertTrue(reply.hasErrors());
+ assertEquals(error, reply.getError(0).getCode());
+ }
+ }
+
+ public void testOOS() throws ListenFailedException, UnknownHostException {
+ Slobrok slobrok = new Slobrok();
+ TestServer srcServer = new TestServer("src", null, slobrok, "oos/*", null);
+ SourceSession srcSession = srcServer.mb.createSourceSession(new Receptor());
+
+ MyServer dst1 = new MyServer("dst1", slobrok, null);
+ MyServer dst2 = new MyServer("dst2", slobrok, null);
+ MyServer dst3 = new MyServer("dst3", slobrok, null);
+ MyServer dst4 = new MyServer("dst4", slobrok, null);
+ MyServer dst5 = new MyServer("dst5", slobrok, null);
+ assertTrue(srcServer.waitSlobrok("*/session", 5));
+
+ // Ensure that normal sending is ok.
+ assertError(srcSession, "dst1/session", ErrorCode.NONE);
+ assertError(srcSession, "dst2/session", ErrorCode.NONE);
+ assertError(srcSession, "dst3/session", ErrorCode.NONE);
+ assertError(srcSession, "dst4/session", ErrorCode.NONE);
+ assertError(srcSession, "dst5/session", ErrorCode.NONE);
+
+ // Ensure that 2 OOS services report properly.
+ OOSServer oosServer = new OOSServer(slobrok, "oos/1", new OOSState()
+ .add("dst2/session", true)
+ .add("dst3/session", true));
+ assertTrue(srcServer.waitSlobrok("oos/*", 1));
+ assertTrue(srcServer.waitState(new OOSState()
+ .add("dst2/session", true)
+ .add("dst3/session", true)));
+ assertError(srcSession, "dst1/session", ErrorCode.NONE);
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst3/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst4/session", ErrorCode.NONE);
+ assertError(srcSession, "dst5/session", ErrorCode.NONE);
+
+ // Ensure that 1 OOS service may come up while other stays down.
+ oosServer.setState(new OOSState().add("dst2/session", true));
+ assertTrue(srcServer.waitState(new OOSState()
+ .add("dst2/session", true)
+ .add("dst3/session", false)));
+ assertError(srcSession, "dst1/session", ErrorCode.NONE);
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst3/session", ErrorCode.NONE);
+ assertError(srcSession, "dst4/session", ErrorCode.NONE);
+ assertError(srcSession, "dst5/session", ErrorCode.NONE);
+
+ // Add another OOS server and make sure that it works properly.
+ OOSServer oosServer2 = new OOSServer(slobrok, "oos/2", new OOSState()
+ .add("dst4/session", true)
+ .add("dst5/session", true));
+ assertTrue(srcServer.waitSlobrok("oos/*", 2));
+ assertTrue(srcServer.waitState(new OOSState()
+ .add("dst2/session", true)
+ .add("dst4/session", true)
+ .add("dst5/session", true)));
+ assertError(srcSession, "dst1/session", ErrorCode.NONE);
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst3/session", ErrorCode.NONE);
+ assertError(srcSession, "dst4/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst5/session", ErrorCode.SERVICE_OOS);
+ oosServer2.shutdown();
+
+ // Ensure that shutting down one OOS server will properly propagate.
+ assertTrue(srcServer.waitSlobrok("oos/*", 1));
+ assertTrue(srcServer.waitState(new OOSState()
+ .add("dst1/session", false)
+ .add("dst2/session", true)
+ .add("dst3/session", false)
+ .add("dst4/session", false)
+ .add("dst5/session", false)));
+ assertError(srcSession, "dst1/session", ErrorCode.NONE);
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst3/session", ErrorCode.NONE);
+ assertError(srcSession, "dst4/session", ErrorCode.NONE);
+ assertError(srcSession, "dst5/session", ErrorCode.NONE);
+
+ // Now add two new OOS servers and make sure that works too.
+ OOSServer oosServer3 = new OOSServer(slobrok, "oos/3", new OOSState()
+ .add("dst2/session", true)
+ .add("dst4/session", true));
+ OOSServer oosServer4 = new OOSServer(slobrok, "oos/4", new OOSState()
+ .add("dst2/session", true)
+ .add("dst3/session", true)
+ .add("dst5/session", true));
+ assertTrue(srcServer.waitSlobrok("oos/*", 3));
+ assertTrue(srcServer.waitState(new OOSState()
+ .add("dst2/session", true)
+ .add("dst3/session", true)
+ .add("dst4/session", true)
+ .add("dst5/session", true)));
+ assertError(srcSession, "dst1/session", ErrorCode.NONE);
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst3/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst4/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst5/session", ErrorCode.SERVICE_OOS);
+
+ // Modify the state of the two new servers and make sure it propagates.
+ oosServer3.setState(new OOSState()
+ .add("dst2/session", true));
+ oosServer4.setState(new OOSState()
+ .add("dst1/session", true));
+ assertTrue(srcServer.waitState(new OOSState()
+ .add("dst1/session", true)
+ .add("dst2/session", true)
+ .add("dst3/session", false)
+ .add("dst4/session", false)
+ .add("dst5/session", false)));
+ assertError(srcSession, "dst1/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst3/session", ErrorCode.NONE);
+ assertError(srcSession, "dst4/session", ErrorCode.NONE);
+ assertError(srcSession, "dst5/session", ErrorCode.NONE);
+ oosServer3.shutdown();
+ oosServer4.shutdown();
+
+ // Ensure that shutting down the two latest OOS servers works properly.
+ assertTrue(srcServer.waitSlobrok("oos/*", 1));
+ assertTrue(srcServer.waitState(new OOSState()
+ .add("dst1/session", false)
+ .add("dst2/session", true)
+ .add("dst3/session", false)
+ .add("dst4/session", false)
+ .add("dst5/session", false)));
+ assertError(srcSession, "dst1/session", ErrorCode.NONE);
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+ assertError(srcSession, "dst3/session", ErrorCode.NONE);
+ assertError(srcSession, "dst4/session", ErrorCode.NONE);
+ assertError(srcSession, "dst5/session", ErrorCode.NONE);
+
+ dst2.destroy();
+ assertTrue(srcServer.waitSlobrok("*/session", 4));
+ assertError(srcSession, "dst2/session", ErrorCode.SERVICE_OOS);
+
+ srcSession.destroy();
+ dst1.destroy();
+ dst2.destroy();
+ dst3.destroy();
+ dst4.destroy();
+ dst5.destroy();
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/RPCNetworkTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/RPCNetworkTestCase.java
new file mode 100644
index 00000000000..b2927611248
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/RPCNetworkTestCase.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.component.Version;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.metrics.MetricSet;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.routing.RoutingPolicy;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.text.Utf8String;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class RPCNetworkTestCase {
+
+ @Test
+ public void requireThatProtocolEncodeExceptionIsCaught() throws Exception {
+ RuntimeException e = new RuntimeException();
+
+ Slobrok slobrok = new Slobrok();
+ TestServer server = new TestServer(new MessageBusParams().addProtocol(MyProtocol.newEncodeException(e)),
+ new RPCNetworkParams().setSlobrokConfigId(slobrok.configId()));
+ Receptor receptor = new Receptor();
+ SourceSession src = server.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(receptor));
+ DestinationSession dst = server.mb.createDestinationSession(new DestinationSessionParams());
+ assertTrue(src.send(new MyMessage().setRoute(Route.parse(dst.getConnectionSpec()))).isAccepted());
+
+ Reply reply = receptor.getReply(60);
+ assertNotNull(reply);
+ assertEquals(1, reply.getNumErrors());
+
+ StringWriter expected = new StringWriter();
+ e.printStackTrace(new PrintWriter(expected));
+
+ String actual = reply.getError(0).toString();
+ assertTrue(actual, actual.contains(expected.toString()));
+ }
+
+ private static class MyMessage extends Message {
+
+ @Override
+ public Utf8String getProtocol() {
+ return new Utf8String(MyProtocol.NAME);
+ }
+
+ @Override
+ public int getType() {
+ return 0;
+ }
+ }
+
+ private static class MyProtocol implements Protocol {
+
+ final static String NAME = "myProtocol";
+ final RuntimeException encodeException;
+
+ MyProtocol(RuntimeException encodeException) {
+ this.encodeException = encodeException;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public byte[] encode(Version version, Routable routable) {
+ throw encodeException;
+ }
+
+ @Override
+ public Routable decode(Version version, byte[] payload) {
+ return null;
+ }
+
+ @Override
+ public RoutingPolicy createPolicy(String name, String param) {
+ return null;
+ }
+
+ @Override
+ public MetricSet getMetrics() {
+ return null;
+ }
+
+ static MyProtocol newEncodeException(RuntimeException e) {
+ return new MyProtocol(e);
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/SendAdapterTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/SendAdapterTestCase.java
new file mode 100755
index 00000000000..d296d7c7058
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/SendAdapterTestCase.java
@@ -0,0 +1,157 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.component.Version;
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.Route;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import com.yahoo.messagebus.test.SimpleReply;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SendAdapterTestCase {
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Setup
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Slobrok slobrok;
+ TestServer srcServer, itrServer, dstServer;
+ SourceSession srcSession;
+ IntermediateSession itrSession;
+ DestinationSession dstSession;
+ TestProtocol srcProtocol, itrProtocol, dstProtocol;
+
+ @Before
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(
+ new MessageBusParams().addProtocol(dstProtocol = new TestProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst")).setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ dstSession = dstServer.mb.createDestinationSession(
+ new DestinationSessionParams().setName("session").setMessageHandler(new Receptor()));
+ itrServer = new TestServer(
+ new MessageBusParams().addProtocol(itrProtocol = new TestProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("itr")).setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ itrSession = itrServer.mb.createIntermediateSession(
+ new IntermediateSessionParams().setName("session").setMessageHandler(new Receptor()).setReplyHandler(new Receptor()));
+ srcServer = new TestServer(
+ new MessageBusParams().addProtocol(srcProtocol = new TestProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(new Receptor()));
+ assertTrue(srcServer.waitSlobrok("*/session", 2));
+ }
+
+ @After
+ public void tearDown() {
+ slobrok.stop();
+ dstSession.destroy();
+ dstServer.destroy();
+ itrSession.destroy();
+ itrServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void requireThatMessagesCanBeSentAcrossAllSupportedVersions() throws Exception {
+ List<Version> versions = Arrays.asList(new Version(5, 0), new Version(5, 1));
+ for (Version srcVersion : versions) {
+ for (Version itrVersion : versions) {
+ for (Version dstVersion : versions) {
+ assertVersionedSend(srcVersion, itrVersion, dstVersion);
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Utilities
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ private void assertVersionedSend(Version srcVersion, Version itrVersion, Version dstVersion) {
+ System.out.println("Sending from " + srcVersion + " through " + itrVersion + " to " + dstVersion + ":");
+ srcServer.net.setVersion(srcVersion);
+ itrServer.net.setVersion(itrVersion);
+ dstServer.net.setVersion(dstVersion);
+
+ Message msg = new SimpleMessage("foo");
+ msg.getTrace().setLevel(9);
+ assertTrue(srcSession.send(msg, Route.parse("itr/session dst/session")).isAccepted());
+ assertNotNull(msg = ((Receptor)itrSession.getMessageHandler()).getMessage(300));
+ System.out.println("\tMessage version " + srcProtocol.lastVersion + " serialized at source.");
+ Version minVersion = srcVersion.compareTo(itrVersion) < 0 ? srcVersion : itrVersion;
+ assertEquals(minVersion, srcProtocol.lastVersion);
+
+ System.out.println("\tMessage version " + itrProtocol.lastVersion + " reached intermediate.");
+ assertEquals(minVersion, itrProtocol.lastVersion);
+ itrSession.forward(msg);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(300));
+ System.out.println("\tMessage version " + itrProtocol.lastVersion + " serialized at intermediate.");
+ minVersion = itrVersion.compareTo(dstVersion) < 0 ? itrVersion : dstVersion;
+ assertEquals(minVersion, itrProtocol.lastVersion);
+
+ System.out.println("\tMessage version " + dstProtocol.lastVersion + " reached destination.");
+ assertEquals(minVersion, dstProtocol.lastVersion);
+ Reply reply = new SimpleReply("bar");
+ reply.swapState(msg);
+ dstSession.reply(reply);
+ assertNotNull(reply = ((Receptor)itrSession.getReplyHandler()).getReply(300));
+ System.out.println("\tReply version " + dstProtocol.lastVersion + " serialized at destination.");
+ assertEquals(minVersion, dstProtocol.lastVersion);
+
+ System.out.println("\tReply version " + itrProtocol.lastVersion + " reached intermediate.");
+ assertEquals(minVersion, itrProtocol.lastVersion);
+ itrSession.forward(reply);
+ assertNotNull(((Receptor)srcSession.getReplyHandler()).getReply(300));
+ System.out.println("\tReply version " + itrProtocol.lastVersion + " serialized at intermediate.");
+ minVersion = srcVersion.compareTo(itrVersion) < 0 ? srcVersion : itrVersion;
+ assertEquals(minVersion, itrProtocol.lastVersion);
+
+ System.out.println("\tReply version " + srcProtocol.lastVersion + " reached source.");
+ assertEquals(minVersion, srcProtocol.lastVersion);
+ }
+
+ private static class TestProtocol extends SimpleProtocol {
+
+ Version lastVersion;
+
+ @Override
+ public byte[] encode(Version version, Routable routable) {
+ lastVersion = version;
+ return super.encode(version, routable);
+ }
+
+ public Routable decode(Version version, byte[] payload) {
+ lastVersion = version;
+ return super.decode(version, payload);
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java
new file mode 100755
index 00000000000..e69896e2bcf
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.slobrok.api.Mirror;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.Identity;
+
+import java.net.UnknownHostException;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ServiceAddressTestCase extends junit.framework.TestCase {
+
+ private Slobrok slobrok;
+ private RPCNetwork network;
+
+ public ServiceAddressTestCase(String msg) {
+ super(msg);
+ }
+
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ network = new RPCNetwork(new RPCNetworkParams()
+ .setIdentity(new Identity("foo"))
+ .setSlobrokConfigId("raw:slobrok[1]\nslobrok[0].connectionspec \"" +
+ new Spec("localhost", slobrok.port()).toString() + "\"\n"));
+ }
+
+ public void tearDown() {
+ network.shutdown();
+ slobrok.stop();
+ }
+
+ public void testAddrServiceAddress() {
+ assertNullAddress("tcp");
+ assertNullAddress("tcp/");
+ assertNullAddress("tcp/localhost");
+ assertNullAddress("tcp/localhost:");
+ assertNullAddress("tcp/localhost:1977");
+ assertNullAddress("tcp/localhost:1977/");
+ assertAddress("tcp/localhost:1977/session", "tcp/localhost:1977", "session");
+ assertNullAddress("tcp/localhost:/session");
+ //assertNullAddress("tcp/:1977/session");
+ assertNullAddress("tcp/:/session");
+ }
+
+ public void testNameServiceAddress() {
+ network.unregisterSession("session");
+ assertTrue(waitSlobrok("foo/session", 0));
+ assertNullAddress("foo/session");
+
+ network.registerSession("session");
+ assertTrue(waitSlobrok("foo/session", 1));
+ assertAddress("foo/session", network.getConnectionSpec(), "session");
+ }
+
+ private boolean waitSlobrok(String pattern, int num) {
+ for (int i = 0; i < 1000 && !Thread.currentThread().isInterrupted(); ++i) {
+ Mirror.Entry[] res = network.getMirror().lookup(pattern);
+ if (res.length == num) {
+ return true;
+ }
+ try {
+ Thread.sleep(10);
+ }
+ catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ return false;
+ }
+
+ private void assertNullAddress(String pattern) {
+ assertNull(new RPCService(network.getMirror(), pattern).resolve());
+ }
+
+ private void assertAddress(String pattern, String expectedSpec, String expectedSession) {
+ RPCService service = new RPCService(network.getMirror(), pattern);
+ RPCServiceAddress obj = service.resolve();
+ assertNotNull(obj);
+ assertNotNull(obj.getConnectionSpec());
+ assertEquals(expectedSpec, obj.getConnectionSpec().toString());
+ if (expectedSession != null) {
+ assertEquals(expectedSession, obj.getSessionName());
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServicePoolTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServicePoolTestCase.java
new file mode 100644
index 00000000000..42580b963a5
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServicePoolTestCase.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import junit.framework.TestCase;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ServicePoolTestCase extends TestCase {
+
+ public void testMaxSize() throws ListenFailedException {
+ Slobrok slobrok = new Slobrok();
+ RPCNetwork net = new RPCNetwork(new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ RPCServicePool pool = new RPCServicePool(net, 2);
+
+ pool.resolve("foo");
+ assertEquals(1, pool.getSize());
+ assertTrue(pool.hasService("foo"));
+ assertTrue(!pool.hasService("bar"));
+ assertTrue(!pool.hasService("baz"));
+
+ pool.resolve("foo");
+ assertEquals(1, pool.getSize());
+ assertTrue(pool.hasService("foo"));
+ assertTrue(!pool.hasService("bar"));
+ assertTrue(!pool.hasService("baz"));
+
+ pool.resolve("bar");
+ assertEquals(2, pool.getSize());
+ assertTrue(pool.hasService("foo"));
+ assertTrue(pool.hasService("bar"));
+ assertTrue(!pool.hasService("baz"));
+
+ pool.resolve("baz");
+ assertEquals(2, pool.getSize());
+ assertTrue(!pool.hasService("foo"));
+ assertTrue(pool.hasService("bar"));
+ assertTrue(pool.hasService("baz"));
+
+ pool.resolve("bar");
+ assertEquals(2, pool.getSize());
+ assertTrue(!pool.hasService("foo"));
+ assertTrue(pool.hasService("bar"));
+ assertTrue(pool.hasService("baz"));
+
+ pool.resolve("foo");
+ assertEquals(2, pool.getSize());
+ assertTrue(pool.hasService("foo"));
+ assertTrue(pool.hasService("bar"));
+ assertTrue(!pool.hasService("baz"));
+
+ slobrok.stop();
+ }
+} \ No newline at end of file
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/SlobrokTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/SlobrokTestCase.java
new file mode 100644
index 00000000000..3f502d4da2f
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/SlobrokTestCase.java
@@ -0,0 +1,151 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.slobrok.api.Mirror;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.network.Identity;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class SlobrokTestCase extends junit.framework.TestCase {
+
+ private static class Res {
+ private List<Mirror.Entry> lst = new ArrayList<>();
+ public Res add(String fullName, String spec) {
+ lst.add(new Mirror.Entry(fullName, spec));
+ return this;
+ }
+ public Mirror.Entry[] toArray() {
+ return lst.toArray(new Mirror.Entry[lst.size()]);
+ }
+ }
+
+
+ public SlobrokTestCase(String message) {
+ super(message);
+ }
+
+ Slobrok slobrok;
+ RPCNetwork net1;
+ RPCNetwork net2;
+ RPCNetwork net3;
+ int port1;
+ int port2;
+ int port3;
+
+ void check(RPCNetwork net, String pattern, Mirror.Entry[] expect) {
+ Comparator<Mirror.Entry> cmp = new Comparator<Mirror.Entry>() {
+ public int compare(Mirror.Entry a, Mirror.Entry b) {
+ return a.compareTo(b);
+ }
+ };
+ Arrays.sort(expect, cmp);
+ Mirror.Entry[] actual = null;
+ for (int i = 0; i < 1000; i++) {
+ actual = net.getMirror().lookup(pattern);
+ Arrays.sort(actual, cmp);
+ if (Arrays.equals(actual, expect)) {
+ System.out.printf("lookup successful for pattern: %s\n", pattern);
+ return;
+ }
+ try { Thread.sleep(10); } catch (InterruptedException e) {
+ //
+ }
+ }
+ System.out.printf("lookup failed for pattern: %s\n", pattern);
+ System.out.printf("actual values:\n");
+ if (actual == null || actual.length == 0) {
+ System.out.printf(" { EMPTY }\n");
+ } else {
+ for (Mirror.Entry entry : actual) {
+ System.out.printf(" { %s, %s }\n", entry.getName(), entry.getSpec());
+ }
+ }
+ System.out.printf("expected values:\n");
+ if (expect.length == 0) {
+ System.out.printf(" { EMPTY }\n");
+ } else {
+ for (Mirror.Entry entry : expect) {
+ System.out.printf(" { %s, %s }\n", entry.getName(), entry.getSpec());
+ }
+ }
+ assertTrue(false);
+ }
+
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ String slobrokCfgId = "raw:slobrok[1]\nslobrok[0].connectionspec \"" + new Spec("localhost", slobrok.port()).toString() + "\"\n";
+ net1 = new RPCNetwork(new RPCNetworkParams().setIdentity(new Identity("net/a")).setSlobrokConfigId(slobrokCfgId));
+ net2 = new RPCNetwork(new RPCNetworkParams().setIdentity(new Identity("net/b")).setSlobrokConfigId(slobrokCfgId));
+ net3 = new RPCNetwork(new RPCNetworkParams().setIdentity(new Identity("net/c")).setSlobrokConfigId(slobrokCfgId));
+ port1 = net1.getPort();
+ port2 = net2.getPort();
+ port3 = net3.getPort();
+ }
+
+ public void tearDown() {
+ net3.shutdown();
+ net2.shutdown();
+ net1.shutdown();
+ slobrok.stop();
+ }
+
+ public void testSlobrok() {
+ net1.registerSession("foo");
+ net2.registerSession("foo");
+ net2.registerSession("bar");
+ net3.registerSession("foo");
+ net3.registerSession("bar");
+ net3.registerSession("baz");
+
+ check(net1, "*/*/*", new Res()
+ .add("net/a/foo", net1.getConnectionSpec())
+ .add("net/b/foo", net2.getConnectionSpec())
+ .add("net/b/bar", net2.getConnectionSpec())
+ .add("net/c/foo", net3.getConnectionSpec())
+ .add("net/c/bar", net3.getConnectionSpec())
+ .add("net/c/baz", net3.getConnectionSpec()).toArray());
+ check(net2, "*/*/*", new Res()
+ .add("net/a/foo", net1.getConnectionSpec())
+ .add("net/b/foo", net2.getConnectionSpec())
+ .add("net/b/bar", net2.getConnectionSpec())
+ .add("net/c/foo", net3.getConnectionSpec())
+ .add("net/c/bar", net3.getConnectionSpec())
+ .add("net/c/baz", net3.getConnectionSpec()).toArray());
+ check(net3, "*/*/*", new Res()
+ .add("net/a/foo", net1.getConnectionSpec())
+ .add("net/b/foo", net2.getConnectionSpec())
+ .add("net/b/bar", net2.getConnectionSpec())
+ .add("net/c/foo", net3.getConnectionSpec())
+ .add("net/c/bar", net3.getConnectionSpec())
+ .add("net/c/baz", net3.getConnectionSpec()).toArray());
+
+ net2.unregisterSession("bar");
+ net3.unregisterSession("bar");
+ net3.unregisterSession("baz");
+
+ check(net1, "*/*/*", new Res()
+ .add("net/a/foo", net1.getConnectionSpec())
+ .add("net/b/foo", net2.getConnectionSpec())
+ .add("net/c/foo", net3.getConnectionSpec()).toArray());
+ check(net2, "*/*/*", new Res()
+ .add("net/a/foo", net1.getConnectionSpec())
+ .add("net/b/foo", net2.getConnectionSpec())
+ .add("net/c/foo", net3.getConnectionSpec()).toArray());
+ check(net3, "*/*/*", new Res()
+ .add("net/a/foo", net1.getConnectionSpec())
+ .add("net/b/foo", net2.getConnectionSpec())
+ .add("net/c/foo", net3.getConnectionSpec()).toArray());
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/TargetPoolTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/TargetPoolTestCase.java
new file mode 100755
index 00000000000..8d58b4dd89f
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/TargetPoolTestCase.java
@@ -0,0 +1,112 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.network.rpc;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Transport;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.concurrent.Timer;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TargetPoolTestCase extends junit.framework.TestCase {
+
+ private Slobrok slobrok;
+ private List<TestServer> servers;
+ private Supervisor orb;
+
+ @Override
+ public void setUp() throws ListenFailedException {
+ slobrok = new Slobrok();
+ servers = new ArrayList<>();
+ orb = new Supervisor(new Transport());
+ }
+
+ @Override
+ public void tearDown() {
+ slobrok.stop();
+ for (TestServer server : servers) {
+ server.destroy();
+ }
+ orb.transport().shutdown().join();
+ }
+
+ public void testConnectionExpire() throws ListenFailedException, UnknownHostException {
+ // Necessary setup to be able to resolve targets.
+ RPCServiceAddress adr1 = registerServer();
+ RPCServiceAddress adr2 = registerServer();
+ RPCServiceAddress adr3 = registerServer();
+
+ PoolTimer timer = new PoolTimer();
+ RPCTargetPool pool = new RPCTargetPool(timer, 0.666);
+
+ // Assert that all connections expire.
+ RPCTarget target;
+ assertNotNull(target = pool.getTarget(orb, adr1)); target.subRef();
+ assertNotNull(target = pool.getTarget(orb, adr2)); target.subRef();
+ assertNotNull(target = pool.getTarget(orb, adr3)); target.subRef();
+ assertEquals(3, pool.size());
+ for (int i = 0; i < 10; ++i) {
+ pool.flushTargets(false);
+ assertEquals(3, pool.size());
+ }
+ timer.millis += 999;
+ pool.flushTargets(false);
+ assertEquals(0, pool.size());
+
+ // Assert that only idle connections expire.
+ assertNotNull(target = pool.getTarget(orb, adr1)); target.subRef();
+ assertNotNull(target = pool.getTarget(orb, adr2)); target.subRef();
+ assertNotNull(target = pool.getTarget(orb, adr3)); target.subRef();
+ assertEquals(3, pool.size());
+ timer.millis += 444;
+ pool.flushTargets(false);
+ assertEquals(3, pool.size());
+ assertNotNull(target = pool.getTarget(orb, adr2)); target.subRef();
+ assertNotNull(target = pool.getTarget(orb, adr3)); target.subRef();
+ assertEquals(3, pool.size());
+ timer.millis += 444;
+ pool.flushTargets(false);
+ assertEquals(2, pool.size());
+ assertNotNull(target = pool.getTarget(orb, adr3)); target.subRef();
+ timer.millis += 444;
+ pool.flushTargets(false);
+ assertEquals(1, pool.size());
+ timer.millis += 444;
+ pool.flushTargets(false);
+ assertEquals(0, pool.size());
+
+ // Assert that connections never expire while they are referenced.
+ assertNotNull(target = pool.getTarget(orb, adr1));
+ assertEquals(1, pool.size());
+ for (int i = 0; i < 10; ++i) {
+ timer.millis += 999;
+ pool.flushTargets(false);
+ assertEquals(1, pool.size());
+ }
+ target.subRef();
+ timer.millis += 999;
+ pool.flushTargets(false);
+ assertEquals(0, pool.size());
+ }
+
+ private RPCServiceAddress registerServer() throws ListenFailedException, UnknownHostException {
+ servers.add(new TestServer("srv" + servers.size(), null, slobrok, null, null));
+ return new RPCServiceAddress("foo/bar", servers.get(servers.size() - 1).mb.getConnectionSpec());
+ }
+
+ private static class PoolTimer implements Timer {
+ long millis = 0;
+
+ @Override
+ public long milliTime() {
+ return millis;
+ }
+ }
+} \ No newline at end of file
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/routing/AdvancedRoutingTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/routing/AdvancedRoutingTestCase.java
new file mode 100755
index 00000000000..62418c2766b
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/routing/AdvancedRoutingTestCase.java
@@ -0,0 +1,119 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.test.CustomPolicyFactory;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+
+import java.net.UnknownHostException;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AdvancedRoutingTestCase extends junit.framework.TestCase {
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Setup
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Slobrok slobrok;
+ TestServer srcServer, dstServer;
+ SourceSession srcSession;
+ DestinationSession dstFoo, dstBar, dstBaz;
+
+ @Override
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst")).setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ dstFoo = dstServer.mb.createDestinationSession(new DestinationSessionParams().setName("foo").setMessageHandler(new Receptor()));
+ dstBar = dstServer.mb.createDestinationSession(new DestinationSessionParams().setName("bar").setMessageHandler(new Receptor()));
+ dstBaz = dstServer.mb.createDestinationSession(new DestinationSessionParams().setName("baz").setMessageHandler(new Receptor()));
+ srcServer = new TestServer(new MessageBusParams().setRetryPolicy(new RetryTransientErrorsPolicy().setBaseDelay(0)).addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(new Receptor()));
+ assertTrue(srcServer.waitSlobrok("dst/*", 3));
+ }
+
+ @Override
+ public void tearDown() {
+ slobrok.stop();
+ dstFoo.destroy();
+ dstBar.destroy();
+ dstBaz.destroy();
+ dstServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ public void testAdvanced() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory(false, ErrorCode.NO_ADDRESS_FOR_SERVICE));
+ srcServer.mb.putProtocol(protocol);
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addHop(new HopSpec("bar", "dst/bar"))
+ .addHop(new HopSpec("baz", "dst/baz"))
+ .addRoute(new RouteSpec("baz").addHop("baz")));
+ Message msg = new SimpleMessage("msg");
+ msg.getTrace().setLevel(9);
+ assertTrue(srcSession.send(msg, Route.parse("[Custom:" + dstFoo.getConnectionSpec() + ",bar,route:baz,dst/cox,?dst/unknown]")).isAccepted());
+
+ // Initial send.
+ assertNotNull(msg = ((Receptor)dstFoo.getMessageHandler()).getMessage(60));
+ dstFoo.acknowledge(msg);
+ assertNotNull(msg = ((Receptor)dstBar.getMessageHandler()).getMessage(60));
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.TRANSIENT_ERROR, "bar"));
+ dstBar.reply(reply);
+ assertNotNull(msg = ((Receptor)dstBaz.getMessageHandler()).getMessage(60));
+ reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.TRANSIENT_ERROR, "baz1"));
+ dstBaz.reply(reply);
+
+ // First retry.
+ assertNull(((Receptor)dstFoo.getMessageHandler()).getMessage(0));
+ assertNotNull(msg = ((Receptor)dstBar.getMessageHandler()).getMessage(60));
+ dstBar.acknowledge(msg);
+ assertNotNull(msg = ((Receptor)dstBaz.getMessageHandler()).getMessage(60));
+ reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.TRANSIENT_ERROR, "baz2"));
+ dstBaz.reply(reply);
+
+ // Second retry.
+ assertNull(((Receptor)dstFoo.getMessageHandler()).getMessage(0));
+ assertNull(((Receptor)dstBar.getMessageHandler()).getMessage(0));
+ assertNotNull(msg = ((Receptor)dstBaz.getMessageHandler()).getMessage(60));
+ reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.FATAL_ERROR, "baz3"));
+ dstBaz.reply(reply);
+
+ // Done.
+ reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(2, reply.getNumErrors());
+ assertEquals(ErrorCode.FATAL_ERROR, reply.getError(0).getCode());
+ assertEquals(ErrorCode.NO_ADDRESS_FOR_SERVICE, reply.getError(1).getCode());
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/routing/ResenderTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/routing/ResenderTestCase.java
new file mode 100755
index 00000000000..48993343f38
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/routing/ResenderTestCase.java
@@ -0,0 +1,200 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+
+import java.net.UnknownHostException;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ResenderTestCase extends junit.framework.TestCase {
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Setup
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Slobrok slobrok;
+ TestServer srcServer, dstServer;
+ SourceSession srcSession;
+ DestinationSession dstSession;
+ RetryTransientErrorsPolicy retryPolicy;
+
+ @Override
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst")).setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ dstSession = dstServer.mb.createDestinationSession(new DestinationSessionParams().setName("session").setMessageHandler(new Receptor()));
+ retryPolicy = new RetryTransientErrorsPolicy();
+ retryPolicy.setBaseDelay(0);
+ srcServer = new TestServer(new MessageBusParams().setRetryPolicy(retryPolicy).addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(new Receptor()));
+ assertTrue(srcServer.waitSlobrok("dst/session", 1));
+ }
+
+ @Override
+ public void tearDown() {
+ slobrok.stop();
+ dstSession.destroy();
+ dstServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ public void testRetryTag() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ for (int i = 0; i < 5; ++i) {
+ assertEquals(i, msg.getRetry());
+ assertEquals(true, msg.getRetryEnabled());
+ replyFromDestination(msg, ErrorCode.APP_TRANSIENT_ERROR, 0);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ }
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertFalse(reply.hasErrors());
+ assertNull(((Receptor)dstSession.getMessageHandler()).getMessage(0));
+ System.out.println(reply.getTrace());
+ }
+
+ public void testRetryEnabledTag() {
+ Message msg = createMessage("msg");
+ msg.setRetryEnabled(false);
+ assertTrue(srcSession.send(msg, Route.parse("dst/session")).isAccepted());
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ assertEquals(false, msg.getRetryEnabled());
+ replyFromDestination(msg, ErrorCode.APP_TRANSIENT_ERROR, 0);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertTrue(reply.hasErrors());
+ assertNull(((Receptor)dstSession.getMessageHandler()).getMessage(0));
+ System.out.println(reply.getTrace());
+ }
+
+ public void testTransientError() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ replyFromDestination(msg, ErrorCode.APP_TRANSIENT_ERROR, 0);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ replyFromDestination(msg, ErrorCode.APP_FATAL_ERROR, 0);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertTrue(reply.hasFatalErrors());
+ assertNull(((Receptor)dstSession.getMessageHandler()).getMessage(0));
+ }
+
+ public void testFatalError() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ replyFromDestination(msg, ErrorCode.APP_FATAL_ERROR, 0);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertTrue(reply.hasFatalErrors());
+ assertNull(((Receptor)dstSession.getMessageHandler()).getMessage(0));
+ }
+
+ public void testDisableRetry() {
+ retryPolicy.setEnabled(false);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ replyFromDestination(msg, ErrorCode.APP_TRANSIENT_ERROR, 0);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertTrue(reply.hasErrors());
+ assertTrue(!reply.hasFatalErrors());
+ assertNull(((Receptor)dstSession.getMessageHandler()).getMessage(0));
+ }
+
+ public void testRetryDelay() {
+ retryPolicy.setBaseDelay(0.01);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ for (int i = 0; i < 5; ++i) {
+ replyFromDestination(msg, ErrorCode.APP_TRANSIENT_ERROR, -1);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ }
+ replyFromDestination(msg, ErrorCode.APP_FATAL_ERROR, 0);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertTrue(reply.hasFatalErrors());
+ assertNull(((Receptor)dstSession.getMessageHandler()).getMessage(0));
+
+ String trace = reply.getTrace().toString();
+ assertTrue(trace.contains("retry 1 in 0.01"));
+ assertTrue(trace.contains("retry 2 in 0.02"));
+ assertTrue(trace.contains("retry 3 in 0.03"));
+ assertTrue(trace.contains("retry 4 in 0.04"));
+ assertTrue(trace.contains("retry 5 in 0.05"));
+ }
+
+ public void testRequestRetryDelay() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ for (int i = 0; i < 5; ++i) {
+ replyFromDestination(msg, ErrorCode.APP_TRANSIENT_ERROR, i / 50.0);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ }
+ replyFromDestination(msg, ErrorCode.APP_FATAL_ERROR, 0);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ assertTrue(reply.hasFatalErrors());
+ assertNull(((Receptor)dstSession.getMessageHandler()).getMessage(0));
+
+ String trace = reply.getTrace().toString();
+ System.out.println(trace);
+ assertTrue(trace.contains("retry 1 in 0"));
+ assertTrue(trace.contains("retry 2 in 0.02"));
+ assertTrue(trace.contains("retry 3 in 0.04"));
+ assertTrue(trace.contains("retry 4 in 0.06"));
+ assertTrue(trace.contains("retry 5 in 0.08"));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Utilities
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ private static Message createMessage(String msg) {
+ SimpleMessage ret = new SimpleMessage(msg);
+ ret.getTrace().setLevel(9);
+ return ret;
+ }
+
+ private void replyFromDestination(Message msg, int errorCode, double retryDelay) {
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ if (errorCode != ErrorCode.NONE) {
+ reply.addError(new Error(errorCode, "err"));
+ }
+ reply.setRetryDelay(retryDelay);
+ dstSession.reply(reply);
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/routing/RetryPolicyTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/routing/RetryPolicyTestCase.java
new file mode 100644
index 00000000000..ad13f3325c7
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/routing/RetryPolicyTestCase.java
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.messagebus.ErrorCode;
+import junit.framework.TestCase;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RetryPolicyTestCase extends TestCase {
+
+ public void testSimpleRetryPolicy() {
+ RetryTransientErrorsPolicy policy = new RetryTransientErrorsPolicy();
+ for (int i = 0; i < 5; ++i) {
+ double delay = i / 3.0;
+ policy.setBaseDelay(delay);
+ for (int j = 0; j < 5; ++j) {
+ assertEquals((int)(j * delay), (int)policy.getRetryDelay(j));
+ }
+ for (int j = ErrorCode.NONE; j < ErrorCode.ERROR_LIMIT; ++j) {
+ policy.setEnabled(true);
+ if (j < ErrorCode.FATAL_ERROR) {
+ assertTrue(policy.canRetry(j));
+ } else {
+ assertFalse(policy.canRetry(j));
+ }
+ policy.setEnabled(false);
+ assertFalse(policy.canRetry(j));
+ }
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/routing/RouteParserTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/routing/RouteParserTestCase.java
new file mode 100755
index 00000000000..e4d120271bc
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/routing/RouteParserTestCase.java
@@ -0,0 +1,168 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RouteParserTestCase extends junit.framework.TestCase {
+
+ public void testHopParser() {
+ Hop hop = Hop.parse("foo");
+ assertNotNull(hop);
+ assertEquals(1, hop.getNumDirectives());
+ assertVerbatimDirective(hop.getDirective(0), "foo");
+
+ assertNotNull(hop = Hop.parse("foo/bar"));
+ assertEquals(2, hop.getNumDirectives());
+ assertVerbatimDirective(hop.getDirective(0), "foo");
+ assertVerbatimDirective(hop.getDirective(1), "bar");
+
+ assertNotNull(hop = Hop.parse("tcp/foo:666/bar"));
+ assertEquals(1, hop.getNumDirectives());
+ assertTcpDirective(hop.getDirective(0), "foo", 666, "bar");
+
+ assertNotNull(hop = Hop.parse("route:foo"));
+ assertEquals(1, hop.getNumDirectives());
+ assertRouteDirective(hop.getDirective(0), "foo");
+
+ assertNotNull(hop = Hop.parse("[Extern:tcp/localhost:3619;foo/bar]"));
+ assertEquals(1, hop.getNumDirectives());
+ assertPolicyDirective(hop.getDirective(0), "Extern","tcp/localhost:3619;foo/bar");
+
+ assertNotNull(hop = Hop.parse("[AND:foo bar]"));
+ assertEquals(1, hop.getNumDirectives());
+ assertPolicyDirective(hop.getDirective(0), "AND","foo bar");
+
+ assertNotNull(hop = Hop.parse("[DocumentRouteSelector:raw:route[2]\n" +
+ "route[0].name \"foo\"\n" +
+ "route[0].selector \"testdoc\"\n" +
+ "route[0].feed \"myfeed\"\n" +
+ "route[1].name \"bar\"\n" +
+ "route[1].selector \"other\"\n" +
+ "route[1].feed \"myfeed\"\n" +
+ "]"));
+ assertEquals(1, hop.getNumDirectives());
+ assertPolicyDirective(hop.getDirective(0), "DocumentRouteSelector",
+ "raw:route[2]\n" +
+ "route[0].name \"foo\"\n" +
+ "route[0].selector \"testdoc\"\n" +
+ "route[0].feed \"myfeed\"\n" +
+ "route[1].name \"bar\"\n" +
+ "route[1].selector \"other\"\n" +
+ "route[1].feed \"myfeed\"");
+
+ assertNotNull(hop = Hop.parse("[DocumentRouteSelector:raw:route[1]\n" +
+ "route[0].name \"docproc/cluster.foo\"\n" +
+ "route[0].selector \"testdoc\"\n" +
+ "route[0].feed \"myfeed\"" +
+ "]"));
+ assertEquals(1, hop.getNumDirectives());
+ assertPolicyDirective(hop.getDirective(0), "DocumentRouteSelector",
+ "raw:route[1]\n" +
+ "route[0].name \"docproc/cluster.foo\"\n" +
+ "route[0].selector \"testdoc\"\n" +
+ "route[0].feed \"myfeed\"");
+ }
+
+ public void testHopParserErrors() {
+ assertError(Hop.parse(""), "Failed to parse empty string.");
+ assertError(Hop.parse("[foo"), "Unterminated '[' in '[foo'");
+ assertError(Hop.parse("foo/[bar]]"), "Unexpected token ']' in 'foo/[bar]]'");
+ assertError(Hop.parse("foo bar"), "Failed to completely parse 'foo bar'.");
+ }
+
+ public void testShortRoute() {
+ Route shortRoute = Route.parse("c");
+ assertNotNull(shortRoute);
+ assertEquals(1, shortRoute.getNumHops());
+ Hop hop = shortRoute.getHop(0);
+ assertNotNull(hop);
+ assertEquals(1, hop.getNumDirectives());
+ assertVerbatimDirective(hop.getDirective(0), "c");
+ }
+
+ public void testShortHops() {
+ Route shortRoute = Route.parse("a b c");
+ assertNotNull(shortRoute);
+ assertEquals(3, shortRoute.getNumHops());
+ Hop hop = shortRoute.getHop(0);
+ assertNotNull(hop);
+ assertEquals(1, hop.getNumDirectives());
+ assertVerbatimDirective(hop.getDirective(0), "a");
+ }
+
+ public void testRouteParser() {
+ Route route = Route.parse("foo bar/baz");
+ assertNotNull(route);
+ assertEquals(2, route.getNumHops());
+ Hop hop = route.getHop(0);
+ assertNotNull(hop);
+ assertEquals(1, hop.getNumDirectives());
+ assertVerbatimDirective(hop.getDirective(0), "foo");
+ assertNotNull(hop = route.getHop(1));
+ assertEquals(2, hop.getNumDirectives());
+ assertVerbatimDirective(hop.getDirective(0), "bar");
+ assertVerbatimDirective(hop.getDirective(1), "baz");
+
+ assertNotNull(route = Route.parse("[Extern:tcp/localhost:3633;itr/session] default"));
+ assertEquals(2, route.getNumHops());
+ assertNotNull(hop = route.getHop(0));
+ assertEquals(1, hop.getNumDirectives());
+ assertPolicyDirective(hop.getDirective(0), "Extern", "tcp/localhost:3633;itr/session");
+ assertNotNull(hop = route.getHop(1));
+ assertEquals(1, hop.getNumDirectives());
+ assertVerbatimDirective(hop.getDirective(0), "default");
+ }
+
+ public void testRouteParserErrors() {
+ assertError(Route.parse(""), "Failed to parse empty string.");
+ assertError(Route.parse("foo [bar"), "Unterminated '[' in '[bar'");
+ assertError(Route.parse("foo bar/[baz]]"), "Unexpected token ']' in 'bar/[baz]]'");
+ }
+
+ private static void assertError(Route route, String msg) {
+ assertNotNull(route);
+ assertEquals(1, route.getNumHops());
+ assertError(route.getHop(0), msg);
+ }
+
+ private static void assertError(Hop hop, String msg) {
+ assertNotNull(hop);
+ System.out.println(hop.toDebugString());
+ assertEquals(1, hop.getNumDirectives());
+ assertErrorDirective(hop.getDirective(0), msg);
+ }
+
+ private static void assertErrorDirective(HopDirective dir, String msg) {
+ assertNotNull(dir);
+ assertTrue(dir instanceof ErrorDirective);
+ assertEquals(msg, ((ErrorDirective)dir).getMessage());
+ }
+
+ private static void assertPolicyDirective(HopDirective dir, String name, String param) {
+ assertNotNull(dir);
+ assertTrue(dir instanceof PolicyDirective);
+ assertEquals(name, ((PolicyDirective)dir).getName());
+ assertEquals(param, ((PolicyDirective)dir).getParam());
+ }
+
+ private static void assertRouteDirective(HopDirective dir, String name) {
+ assertNotNull(dir);
+ assertTrue(dir instanceof RouteDirective);
+ assertEquals(name, ((RouteDirective)dir).getName());
+ }
+
+ private static void assertTcpDirective(HopDirective dir, String host, int port, String session) {
+ assertNotNull(dir);
+ assertTrue(dir instanceof TcpDirective);
+ assertEquals(host, ((TcpDirective)dir).getHost());
+ assertEquals(port, ((TcpDirective)dir).getPort());
+ assertEquals(session, ((TcpDirective)dir).getSession());
+ }
+
+ private static void assertVerbatimDirective(HopDirective dir, String image) {
+ assertNotNull(dir);
+ assertTrue(dir instanceof VerbatimDirective);
+ assertEquals(image, ((VerbatimDirective)dir).getImage());
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingContextTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingContextTestCase.java
new file mode 100755
index 00000000000..ea217af5b9a
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingContextTestCase.java
@@ -0,0 +1,258 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingContextTestCase extends junit.framework.TestCase {
+ public static final int TIMEOUT_SECS = 120;
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Setup
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Slobrok slobrok;
+ TestServer srcServer, dstServer;
+ SourceSession srcSession;
+ DestinationSession dstSession;
+
+ @Override
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst")).setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ dstSession = dstServer.mb.createDestinationSession(new DestinationSessionParams().setName("session").setMessageHandler(new Receptor()));
+ srcServer = new TestServer(new MessageBusParams().setRetryPolicy(new RetryTransientErrorsPolicy().setBaseDelay(0)).addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setReplyHandler(new Receptor()));
+ assertTrue(srcServer.waitSlobrok("dst/session", 1));
+ }
+
+ @Override
+ public void tearDown() {
+ slobrok.stop();
+ dstSession.destroy();
+ dstServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ public void testSingleDirective() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory(
+ false,
+ Arrays.asList("foo", "bar", "baz/cox"),
+ Arrays.asList("foo", "bar")));
+ srcServer.mb.putProtocol(protocol);
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("myroute").addHop("myhop"))
+ .addHop(new HopSpec("myhop", "[Custom]")
+ .addRecipient("foo").addRecipient("bar").addRecipient("baz/cox")));
+ for (int i = 0; i < 2; ++i) {
+ assertTrue(srcSession.send(createMessage("msg"), "myroute").isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(TIMEOUT_SECS);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+ }
+
+ public void testMoreDirectives() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory(
+ false,
+ Arrays.asList("foo", "foo/bar", "foo/bar0/baz", "foo/bar1/baz", "foo/bar/baz/cox"),
+ Arrays.asList("foo/bar0/baz", "foo/bar1/baz")));
+ srcServer.mb.putProtocol(protocol);
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("myroute").addHop("myhop"))
+ .addHop(new HopSpec("myhop", "foo/[Custom]/baz")
+ .addRecipient("foo").addRecipient("foo/bar")
+ .addRecipient("foo/bar0/baz").addRecipient("foo/bar1/baz")
+ .addRecipient("foo/bar/baz/cox")));
+ for (int i = 0; i < 2; ++i) {
+ assertTrue(srcSession.send(createMessage("msg"), "myroute").isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(TIMEOUT_SECS);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+ }
+
+ public void testRecipientsRemain() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("First", new CustomPolicyFactory(true, Arrays.asList("foo/bar"), Arrays.asList("foo/[Second]")));
+ protocol.addPolicyFactory("Second", new CustomPolicyFactory(false, Arrays.asList("foo/bar"), Arrays.asList("foo/bar")));
+ srcServer.mb.putProtocol(protocol);
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("myroute").addHop("myhop"))
+ .addHop(new HopSpec("myhop", "[First]/[Second]")
+ .addRecipient("foo/bar")));
+ for (int i = 0; i < 2; ++i) {
+ assertTrue(srcSession.send(createMessage("msg"), "myroute").isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(TIMEOUT_SECS);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+ }
+
+ public void testConstRoute() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("DocumentRouteSelector",
+ new CustomPolicyFactory(true, Arrays.asList("dst"), Arrays.asList("dst")));
+ srcServer.mb.putProtocol(protocol);
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("default").addHop("indexing"))
+ .addHop(new HopSpec("indexing", "[DocumentRouteSelector]").addRecipient("dst"))
+ .addHop(new HopSpec("dst", "dst/session")));
+ for (int i = 0; i < 2; ++i) {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("route:default")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(TIMEOUT_SECS);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(TIMEOUT_SECS);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Utilities
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ private Message createMessage(String msg) {
+ Message ret = new SimpleMessage(msg);
+ ret.getTrace().setLevel(9);
+ return ret;
+ }
+
+ private static class CustomPolicyFactory implements SimpleProtocol.PolicyFactory {
+
+ final boolean forward;
+ final List<String> expectedAll;
+ final List<String> expectedMatched;
+
+ public CustomPolicyFactory(boolean forward, List<String> all, List<String> matched) {
+ this.forward = forward;
+ this.expectedAll = all;
+ this.expectedMatched = matched;
+ }
+
+ public RoutingPolicy create(String param) {
+ return new CustomPolicy(this);
+ }
+ }
+
+ private static class CustomPolicy implements RoutingPolicy {
+
+ CustomPolicyFactory factory;
+
+ public CustomPolicy(CustomPolicyFactory factory) {
+ this.factory = factory;
+ }
+
+ public void select(RoutingContext ctx) {
+ Reply reply = new EmptyReply();
+ reply.getTrace().setLevel(9);
+
+ List<Route> recipients = ctx.getAllRecipients();
+ if (factory.expectedAll.size() == recipients.size()) {
+ ctx.trace(1, "Got " + recipients.size() + " expected recipients.");
+ for (Route route : recipients) {
+ if (factory.expectedAll.contains(route.toString())) {
+ ctx.trace(1, "Got expected recipient '" + route + "'.");
+ } else {
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR,
+ "Recipient '" + route + "' not expected."));
+ }
+ }
+ } else {
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR,
+ "Expected " + factory.expectedAll.size() + " recipients, got " + recipients.size() + "."));
+ }
+
+ if (ctx.getNumRecipients() == recipients.size()) {
+ for (int i = 0; i < recipients.size(); ++i) {
+ if (recipients.get(i) == ctx.getRecipient(i)) {
+ ctx.trace(1, "getRecipient(" + i + ") matches getAllRecipients().get(" + i + ")");
+ } else {
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR,
+ "getRecipient(" + i + ") differs from getAllRecipients().get(" + i + ")"));
+ }
+ }
+ } else {
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR,
+ "getNumRecipients() differs from getAllRecipients().size()"));
+ }
+
+ recipients = ctx.getMatchedRecipients();
+ if (factory.expectedMatched.size() == recipients.size()) {
+ ctx.trace(1, "Got " + recipients.size() + " matched recipients.");
+ for (Route route : recipients) {
+ if (factory.expectedMatched.contains(route.toString())) {
+ ctx.trace(1, "Got matched recipient '" + route + "'.");
+ } else {
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR,
+ "Matched recipient '" + route + "' not expected."));
+ }
+ }
+ } else {
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR,
+ "Expected " + factory.expectedAll.size() + " matched recipients, got " + recipients.size() + "."));
+ }
+
+ if (!reply.hasErrors() && factory.forward) {
+ for (Route route : recipients) {
+ ctx.addChild(route);
+ }
+ } else {
+ ctx.setReply(reply);
+ }
+ }
+
+ public void merge(RoutingContext ctx) {
+ Reply ret = new EmptyReply();
+ for (RoutingNodeIterator it = ctx.getChildIterator();
+ it.isValid(); it.next()) {
+ Reply reply = it.getReplyRef();
+ for (int i = 0; i < reply.getNumErrors(); ++i) {
+ ret.addError(reply.getError(i));
+ }
+ }
+ ctx.setReply(ret);
+ }
+
+ @Override
+ public void destroy() {
+ }
+ }
+
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingSpecTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingSpecTestCase.java
new file mode 100755
index 00000000000..3cbb9d86e44
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingSpecTestCase.java
@@ -0,0 +1,336 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.messagebus.ConfigAgent;
+import com.yahoo.messagebus.ConfigHandler;
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingSpecTestCase extends TestCase {
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ public void testConfig() {
+ assertConfig(new RoutingSpec());
+ assertConfig(new RoutingSpec().addTable(new RoutingTableSpec("mytable1")));
+ assertConfig(new RoutingSpec().addTable(new RoutingTableSpec("mytable1")
+ .addHop(new HopSpec("myhop1", "myselector1"))));
+ assertConfig(new RoutingSpec().addTable(new RoutingTableSpec("mytable1")
+ .addHop(new HopSpec("myhop1", "myselector1"))
+ .addRoute(new RouteSpec("myroute1").addHop("myhop1"))));
+ assertConfig(new RoutingSpec().addTable(new RoutingTableSpec("mytable1")
+ .addHop(new HopSpec("myhop1", "myselector1"))
+ .addHop(new HopSpec("myhop2", "myselector2"))
+ .addRoute(new RouteSpec("myroute1").addHop("myhop1"))
+ .addRoute(new RouteSpec("myroute2").addHop("myhop2"))
+ .addRoute(new RouteSpec("myroute12").addHop("myhop1").addHop("myhop2"))));
+ assertConfig(new RoutingSpec().addTable(new RoutingTableSpec("mytable1")
+ .addHop(new HopSpec("myhop1", "myselector1"))
+ .addHop(new HopSpec("myhop2", "myselector2"))
+ .addRoute(new RouteSpec("myroute1").addHop("myhop1"))
+ .addRoute(new RouteSpec("myroute2").addHop("myhop2"))
+ .addRoute(new RouteSpec("myroute12").addHop("myhop1").addHop("myhop2")))
+ .addTable(new RoutingTableSpec("mytable2")));
+ assertEquals("routingtable[2]\n" +
+ "routingtable[0].protocol \"mytable1\"\n" +
+ "routingtable[1].protocol \"mytable2\"\n" +
+ "routingtable[1].hop[3]\n" +
+ "routingtable[1].hop[0].name \"myhop1\"\n" +
+ "routingtable[1].hop[0].selector \"myselector1\"\n" +
+ "routingtable[1].hop[1].name \"myhop2\"\n" +
+ "routingtable[1].hop[1].selector \"myselector2\"\n" +
+ "routingtable[1].hop[1].ignoreresult true\n" +
+ "routingtable[1].hop[2].name \"myhop1\"\n" +
+ "routingtable[1].hop[2].selector \"myselector3\"\n" +
+ "routingtable[1].hop[2].recipient[2]\n" +
+ "routingtable[1].hop[2].recipient[0] \"myrecipient1\"\n" +
+ "routingtable[1].hop[2].recipient[1] \"myrecipient2\"\n" +
+ "routingtable[1].route[1]\n" +
+ "routingtable[1].route[0].name \"myroute1\"\n" +
+ "routingtable[1].route[0].hop[1]\n" +
+ "routingtable[1].route[0].hop[0] \"myhop1\"\n",
+ new RoutingSpec()
+ .addTable(new RoutingTableSpec("mytable1"))
+ .addTable(new RoutingTableSpec("mytable2")
+ .addHop(new HopSpec("myhop1", "myselector1"))
+ .addHop(new HopSpec("myhop2", "myselector2").setIgnoreResult(true))
+ .addHop(new HopSpec("myhop1", "myselector3")
+ .addRecipient("myrecipient1")
+ .addRecipient("myrecipient2"))
+ .addRoute(new RouteSpec("myroute1").addHop("myhop1"))).toString());
+ }
+
+ public void testApplicationSpec() {
+ assertApplicationSpec(Arrays.asList("foo"),
+ Arrays.asList("foo",
+ "*"));
+ assertApplicationSpec(Arrays.asList("foo/bar"),
+ Arrays.asList("foo/bar",
+ "foo/*",
+ "*/bar",
+ "*/*"));
+ assertApplicationSpec(Arrays.asList("foo/0/baz",
+ "foo/1/baz",
+ "foo/2/baz"),
+ Arrays.asList("foo/0/baz",
+ "foo/1/baz",
+ "foo/2/baz",
+ "foo/0/*",
+ "foo/1/*",
+ "foo/2/*",
+ "foo/*/baz",
+ "*/0/baz",
+ "*/1/baz",
+ "*/2/baz",
+ "foo/*/*",
+ "*/0/*",
+ "*/1/*",
+ "*/2/*",
+ "*/*/baz",
+ "*/*/*"));
+ }
+
+ public void testVeriyfOk() {
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("hop1", "myservice1"))),
+ new ApplicationSpec().addService("mytable", "myservice1"));
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addRoute(new RouteSpec("route1").addHop("myservice1"))),
+ new ApplicationSpec().addService("mytable", "myservice1"));
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("hop1", "myservice1"))
+ .addRoute(new RouteSpec("route1").addHop("hop1"))),
+ new ApplicationSpec().addService("mytable", "myservice1"));
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("hop1", "route:route2"))
+ .addHop(new HopSpec("hop2", "myservice1"))
+ .addRoute(new RouteSpec("route1").addHop("hop1"))
+ .addRoute(new RouteSpec("route2").addHop("hop2"))),
+ new ApplicationSpec().addService("mytable", "myservice1"));
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("myhop1", "foo/[bar]/baz").addRecipient("foo/0/baz").addRecipient("foo/1/baz"))),
+ new ApplicationSpec()
+ .addService("mytable", "foo/0/baz")
+ .addService("mytable", "foo/1/baz"));
+ }
+
+ public void testVerifyToggle() {
+ assertVerifyOk(new RoutingSpec(false)
+ .addTable(new RoutingTableSpec("mytable"))
+ .addTable(new RoutingTableSpec("mytable")),
+ new ApplicationSpec());
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable", false)
+ .addHop(new HopSpec("foo", "bar"))
+ .addHop(new HopSpec("foo", "baz"))),
+ new ApplicationSpec());
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "", false))),
+ new ApplicationSpec());
+ assertVerifyOk(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addRoute(new RouteSpec("foo", false))),
+ new ApplicationSpec());
+ }
+
+ public void testVerifyFail() {
+ // Duplicate table.
+ assertVerifyFail(new RoutingSpec()
+ .addTable(new RoutingTableSpec("mytable"))
+ .addTable(new RoutingTableSpec("mytable")),
+ new ApplicationSpec(),
+ Arrays.asList("Routing table 'mytable' is defined 2 times."));
+
+ // Duplicate hop.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "bar"))
+ .addHop(new HopSpec("foo", "baz"))),
+ new ApplicationSpec()
+ .addService("mytable", "bar")
+ .addService("mytable", "baz"),
+ Arrays.asList("Hop 'foo' in routing table 'mytable' is defined 2 times."));
+
+ // Duplicate route.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addRoute(new RouteSpec("foo").addHop("bar"))
+ .addRoute(new RouteSpec("foo").addHop("baz"))),
+ new ApplicationSpec()
+ .addService("mytable", "bar")
+ .addService("mytable", "baz"),
+ Arrays.asList("Route 'foo' in routing table 'mytable' is defined 2 times."));
+
+ // Empty hop.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", ""))),
+ new ApplicationSpec(),
+ Arrays.asList("For hop 'foo' in routing table 'mytable'; Failed to parse empty string."));
+
+ // Empty route.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addRoute(new RouteSpec("foo"))),
+ new ApplicationSpec(),
+ Arrays.asList("Route 'foo' in routing table 'mytable' has no hops."));
+
+ // Hop error.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "bar/baz cox"))),
+ new ApplicationSpec(),
+ Arrays.asList("For hop 'foo' in routing table 'mytable'; Failed to completely parse 'bar/baz cox'."));
+
+ // Hop error in recipient.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "[bar]").addRecipient("bar/baz cox"))),
+ new ApplicationSpec(),
+ Arrays.asList("For recipient 'bar/baz cox' in hop 'foo' in routing table 'mytable'; Failed to completely parse 'bar/baz cox'."));
+
+ // Hop error in route.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addRoute(new RouteSpec("foo").addHop("bar/baz cox"))),
+ new ApplicationSpec(),
+ Arrays.asList("For hop 1 in route 'foo' in routing table 'mytable'; Failed to completely parse 'bar/baz cox'."));
+
+ // Hop not found.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addRoute(new RouteSpec("foo").addHop("bar"))),
+ new ApplicationSpec(),
+ Arrays.asList("Hop 1 in route 'foo' in routing table 'mytable' references 'bar' which is neither a service, a route nor another hop."));
+
+ // Mismatched recipient.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "bar/[baz]/cox").addRecipient("cox/0/bar"))),
+ new ApplicationSpec(),
+ Arrays.asList("Selector 'bar/[baz]/cox' does not match recipient 'cox/0/bar' in hop 'foo' in routing table 'mytable'."));
+
+ // Route not found.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "route:bar"))),
+ new ApplicationSpec(),
+ Arrays.asList("Hop 'foo' in routing table 'mytable' references route 'bar' which does not exist."));
+
+ // Route not found in route.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addRoute(new RouteSpec("foo").addHop("route:bar"))),
+ new ApplicationSpec(),
+ Arrays.asList("Hop 1 in route 'foo' in routing table 'mytable' references route 'bar' which does not exist."));
+
+ // Service not found.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "bar/baz"))),
+ new ApplicationSpec(),
+ Arrays.asList("Hop 'foo' in routing table 'mytable' references 'bar/baz' which is neither a service, a route nor another hop."));
+
+ // Unexpected recipient.
+ assertVerifyFail(new RoutingSpec().addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("foo", "bar").addRecipient("baz"))),
+ new ApplicationSpec()
+ .addService("mytable", "bar")
+ .addService("mytable", "baz"),
+ Arrays.asList("Hop 'foo' in routing table 'mytable' has recipients but no policy directive."));
+
+ // Multiple errors.
+ assertVerifyFail(new RoutingSpec()
+ .addTable(new RoutingTableSpec("mytable"))
+ .addTable(new RoutingTableSpec("mytable")
+ .addHop(new HopSpec("hop1", "bar"))
+ .addHop(new HopSpec("hop1", "baz"))
+ .addHop(new HopSpec("hop2", ""))
+ .addHop(new HopSpec("hop3", "bar/baz cox"))
+ .addHop(new HopSpec("hop4", "[bar]").addRecipient("bar/baz cox"))
+ .addHop(new HopSpec("hop5", "bar/[baz]/cox").addRecipient("cox/0/bar"))
+ .addHop(new HopSpec("hop6", "route:route69"))
+ .addHop(new HopSpec("hop7", "bar/baz"))
+ .addHop(new HopSpec("hop8", "bar").addRecipient("baz"))
+ .addRoute(new RouteSpec("route1").addHop("bar"))
+ .addRoute(new RouteSpec("route1").addHop("baz"))
+ .addRoute(new RouteSpec("route2").addHop(""))
+ .addRoute(new RouteSpec("route3").addHop("bar/baz cox"))
+ .addRoute(new RouteSpec("route4").addHop("hop69"))
+ .addRoute(new RouteSpec("route5").addHop("route:route69"))),
+ new ApplicationSpec()
+ .addService("mytable", "bar")
+ .addService("mytable", "baz"),
+ Arrays.asList("Routing table 'mytable' is defined 2 times.",
+ "For hop 'hop2' in routing table 'mytable'; Failed to parse empty string.",
+ "For hop 'hop3' in routing table 'mytable'; Failed to completely parse 'bar/baz cox'.",
+ "For hop 1 in route 'route2' in routing table 'mytable'; Failed to parse empty string.",
+ "For hop 1 in route 'route3' in routing table 'mytable'; Failed to completely parse 'bar/baz cox'.",
+ "For recipient 'bar/baz cox' in hop 'hop4' in routing table 'mytable'; Failed to completely parse 'bar/baz cox'.",
+ "Hop 'hop1' in routing table 'mytable' is defined 2 times.",
+ "Hop 'hop6' in routing table 'mytable' references route 'route69' which does not exist.",
+ "Hop 'hop7' in routing table 'mytable' references 'bar/baz' which is neither a service, a route nor another hop.",
+ "Hop 'hop8' in routing table 'mytable' has recipients but no policy directive.",
+ "Hop 1 in route 'route4' in routing table 'mytable' references 'hop69' which is neither a service, a route nor another hop.",
+ "Hop 1 in route 'route5' in routing table 'mytable' references route 'route69' which does not exist.",
+ "Route 'route1' in routing table 'mytable' is defined 2 times.",
+ "Selector 'bar/[baz]/cox' does not match recipient 'cox/0/bar' in hop 'hop5' in routing table 'mytable'."));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Utilities
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ private static void assertVerifyOk(RoutingSpec routing, ApplicationSpec app) {
+ assertVerifyFail(routing, app, new ArrayList<String>());
+ }
+
+ private static void assertVerifyFail(RoutingSpec routing, ApplicationSpec app, List<String> expectedErrors) {
+ List<String> errors = new ArrayList<>();
+ routing.verify(app, errors);
+
+ Collections.sort(errors);
+ Collections.sort(expectedErrors);
+ assertEquals(expectedErrors.toString(), errors.toString());
+ }
+
+ private static void assertConfig(RoutingSpec routing) {
+ assertEquals(routing, routing);
+ assertEquals(routing, new RoutingSpec(routing));
+
+ ConfigStore store = new ConfigStore();
+ ConfigAgent subscriber = new ConfigAgent("raw:" + routing.toString(), store);
+ subscriber.subscribe();
+ assertTrue(store.routing.equals(routing));
+ }
+
+ private static void assertApplicationSpec(List<String> services, List<String> patterns) {
+ ApplicationSpec app = new ApplicationSpec();
+ for (String pattern : patterns) {
+ assertFalse(app.isService("foo", pattern));
+ assertFalse(app.isService("bar", pattern));
+ }
+ for (String service : services) {
+ app.addService("foo", service);
+ }
+ for (String pattern : patterns) {
+ assertTrue(app.isService("foo", pattern));
+ assertFalse(app.isService("bar", pattern));
+ }
+ for (String service : services) {
+ app.addService("bar", service);
+ }
+ for (String pattern : patterns) {
+ assertTrue(app.isService("foo", pattern));
+ assertTrue(app.isService("bar", pattern));
+ }
+ }
+
+ private static class ConfigStore implements ConfigHandler {
+
+ RoutingSpec routing = null;
+
+ public void setupRouting(RoutingSpec routing) {
+ this.routing = routing;
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingTestCase.java
new file mode 100644
index 00000000000..911350d3cc9
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/routing/RoutingTestCase.java
@@ -0,0 +1,1144 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.routing;
+
+import com.yahoo.component.Vtag;
+import com.yahoo.jrt.ListenFailedException;
+import com.yahoo.jrt.slobrok.server.Slobrok;
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.Error;
+import com.yahoo.messagebus.network.Identity;
+import com.yahoo.messagebus.network.rpc.RPCNetworkParams;
+import com.yahoo.messagebus.network.rpc.test.TestServer;
+import com.yahoo.messagebus.routing.test.CustomPolicy;
+import com.yahoo.messagebus.routing.test.CustomPolicyFactory;
+import com.yahoo.messagebus.test.Receptor;
+import com.yahoo.messagebus.test.SimpleMessage;
+import com.yahoo.messagebus.test.SimpleProtocol;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RoutingTestCase {
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Setup
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Slobrok slobrok;
+ TestServer srcServer, dstServer;
+ SourceSession srcSession;
+ DestinationSession dstSession;
+ RetryTransientErrorsPolicy retryPolicy;
+
+ @Before
+ public void setUp() throws ListenFailedException, UnknownHostException {
+ slobrok = new Slobrok();
+ dstServer = new TestServer(new MessageBusParams().addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setIdentity(new Identity("dst")).setSlobrokConfigId(
+ TestServer.getSlobrokConfig(slobrok)));
+ dstSession = dstServer.mb.createDestinationSession(
+ new DestinationSessionParams().setName("session").setMessageHandler(new Receptor()));
+ retryPolicy = new RetryTransientErrorsPolicy();
+ retryPolicy.setBaseDelay(0);
+ srcServer = new TestServer(new MessageBusParams().setRetryPolicy(retryPolicy).addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams().setSlobrokConfigId(TestServer.getSlobrokConfig(slobrok)));
+ srcSession = srcServer.mb.createSourceSession(
+ new SourceSessionParams().setTimeout(600.0).setThrottlePolicy(null).setReplyHandler(new Receptor()));
+ assertTrue(srcServer.waitSlobrok("dst/session", 1));
+ }
+
+ @After
+ public void tearDown() {
+ slobrok.stop();
+ dstSession.destroy();
+ dstServer.destroy();
+ srcSession.destroy();
+ srcServer.destroy();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void requireThatNullRouteIsCaught() {
+ assertTrue(srcSession.send(createMessage("msg")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.ILLEGAL_ROUTE, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatEmptyRouteIsCaught() {
+ assertTrue(srcSession.send(createMessage("msg"), new Route()).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.ILLEGAL_ROUTE, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatHopNameIsExpanded() {
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addHop(new HopSpec("dst", "dst/session")));
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+
+ @Test
+ public void requireThatRouteDirectiveWorks() {
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("dst").addHop("dst/session"))
+ .addHop(new HopSpec("dir", "route:dst")));
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dir")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+
+ @Test
+ public void requireThatRouteNameIsExpanded() {
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("dst").addHop("dst/session")));
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+
+ @Test
+ public void requireThatHopResolutionOverflowIsCaught() {
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addHop(new HopSpec("foo", "bar"))
+ .addHop(new HopSpec("bar", "foo")));
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("foo")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.ILLEGAL_ROUTE, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatRouteResolutionOverflowIsCaught() {
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("foo").addHop("route:foo")));
+ assertTrue(srcSession.send(createMessage("msg"), "foo").isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.ILLEGAL_ROUTE, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatRouteExpansionOnlyReplacesFirstHop() {
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addRoute(new RouteSpec("foo").addHop("dst/session").addHop("bar")));
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("route:foo baz")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ assertEquals(2, msg.getRoute().getNumHops());
+ assertEquals("bar", msg.getRoute().getHop(0).toString());
+ assertEquals("baz", msg.getRoute().getHop(1).toString());
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+
+ @Test
+ public void requireThatErrorDirectiveWorks() {
+ Route route = Route.parse("foo/bar/baz");
+ route.getHop(0).setDirective(1, new ErrorDirective("err"));
+ assertTrue(srcSession.send(createMessage("msg"), route).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.ILLEGAL_ROUTE, reply.getError(0).getCode());
+ assertEquals("err", reply.getError(0).getMessage());
+ }
+
+ @Test
+ public void requireThatIllegalSelectIsCaught() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory());
+ srcServer.mb.putProtocol(protocol);
+ Route route = Route.parse("[Custom: ]");
+ assertNotNull(route);
+ assertTrue(srcSession.send(createMessage("msg"), route).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.NO_SERVICES_FOR_ROUTE, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatEmptySelectIsCaught() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory());
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom]")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.NO_SERVICES_FOR_ROUTE, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatPolicySelectWorks() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory());
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom:dst/session]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+
+ @Test
+ public void requireThatTransientErrorsAreRetried() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "err1"));
+ dstSession.reply(reply);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "err2"));
+ dstSession.reply(reply);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ dstSession.acknowledge(msg);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList("[APP_TRANSIENT_ERROR @ localhost]: err1",
+ "-[APP_TRANSIENT_ERROR @ localhost]: err1",
+ "[APP_TRANSIENT_ERROR @ localhost]: err2",
+ "-[APP_TRANSIENT_ERROR @ localhost]: err2"),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatTransientErrorsAreRetriedWithPolicy() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory());
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom:dst/session]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "err1"));
+ dstSession.reply(reply);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "err2"));
+ dstSession.reply(reply);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ dstSession.acknowledge(msg);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList("Source session accepted a 3 byte message. 1 message(s) now pending.",
+ "Running routing policy 'Custom'.",
+ "Selecting [dst/session].",
+ "Component 'dst/session' selected by policy 'Custom'.",
+ "Resolving 'dst/session'.",
+ "Sending message (version ${VERSION}) from client to 'dst/session'",
+ "Message (type 1) received at 'dst' for session 'session'.",
+ "[APP_TRANSIENT_ERROR @ localhost]: err1",
+ "Sending reply (version ${VERSION}) from 'dst'.",
+ "Reply (type 0) received at client.",
+ "Routing policy 'Custom' merging replies.",
+ "Merged [dst/session].",
+ "Message scheduled for retry 1 in 0.0 seconds.",
+ "Resender resending message.",
+ "Running routing policy 'Custom'.",
+ "Selecting [dst/session].",
+ "Component 'dst/session' selected by policy 'Custom'.",
+ "Resolving 'dst/session'.",
+ "Sending message (version ${VERSION}) from client to 'dst/session'",
+ "Message (type 1) received at 'dst' for session 'session'.",
+ "[APP_TRANSIENT_ERROR @ localhost]: err2",
+ "Sending reply (version ${VERSION}) from 'dst'.",
+ "Reply (type 0) received at client.",
+ "Routing policy 'Custom' merging replies.",
+ "Merged [dst/session].",
+ "Message scheduled for retry 2 in 0.0 seconds.",
+ "Resender resending message.",
+ "Running routing policy 'Custom'.",
+ "Selecting [dst/session].",
+ "Component 'dst/session' selected by policy 'Custom'.",
+ "Resolving 'dst/session'.",
+ "Sending message (version ${VERSION}) from client to 'dst/session'",
+ "Message (type 1) received at 'dst' for session 'session'.",
+ "Sending reply (version ${VERSION}) from 'dst'.",
+ "Reply (type 0) received at client.",
+ "Routing policy 'Custom' merging replies.",
+ "Merged [dst/session].",
+ "Source session received reply. 0 message(s) now pending."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatRetryCanBeDisabled() {
+ retryPolicy.setEnabled(false);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "err"));
+ dstSession.reply(reply);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.APP_TRANSIENT_ERROR, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatRetryCallsSelect() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory());
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom:dst/session]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "err"));
+ dstSession.reply(reply);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ dstSession.acknowledge(msg);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList("Selecting [dst/session].",
+ "[APP_TRANSIENT_ERROR @ localhost]",
+ "-[APP_TRANSIENT_ERROR @ localhost]",
+ "Merged [dst/session].",
+ "Selecting [dst/session].",
+ "Sending reply",
+ "Merged [dst/session]."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatPolicyCanDisableReselectOnRetry() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory(false));
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom:dst/session]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "err"));
+ dstSession.reply(reply);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ dstSession.acknowledge(msg);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList("Selecting [dst/session].",
+ "[APP_TRANSIENT_ERROR @ localhost]",
+ "-[APP_TRANSIENT_ERROR @ localhost]",
+ "Merged [dst/session].",
+ "-Selecting [dst/session].",
+ "Sending reply",
+ "Merged [dst/session]."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatPolicyCanConsumeErrors() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory(true, ErrorCode.NO_ADDRESS_FOR_SERVICE));
+ srcServer.mb.putProtocol(protocol);
+ retryPolicy.setEnabled(false);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom:dst/session,dst/unknown]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.NO_ADDRESS_FOR_SERVICE, reply.getError(0).getCode());
+ assertTrace(Arrays.asList("Selecting [dst/session, dst/unknown].",
+ "[NO_ADDRESS_FOR_SERVICE @ localhost]",
+ "Sending reply",
+ "Merged [dst/session, dst/unknown]."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatPolicyOnlyConsumesDeclaredErrors() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory());
+ srcServer.mb.putProtocol(protocol);
+ retryPolicy.setEnabled(false);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom:dst/unknown]")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.NO_ADDRESS_FOR_SERVICE, reply.getError(0).getCode());
+ assertTrace(Arrays.asList("Selecting [dst/unknown].",
+ "[NO_ADDRESS_FOR_SERVICE @ localhost]",
+ "Merged [dst/unknown]."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatPolicyCanExpandToPolicy() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory(true, ErrorCode.NO_ADDRESS_FOR_SERVICE));
+ srcServer.mb.putProtocol(protocol);
+ retryPolicy.setEnabled(false);
+ assertTrue(srcSession.send(createMessage("msg"),
+ Route.parse("[Custom:[Custom:dst/session],[Custom:dst/unknown]]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.NO_ADDRESS_FOR_SERVICE, reply.getError(0).getCode());
+ }
+
+ @Test
+ public void requireThatReplyCanBeRemovedFromChildNodes() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new RemoveReplyPolicy(true,
+ Arrays.asList(ErrorCode.NO_ADDRESS_FOR_SERVICE),
+ CustomPolicyFactory.parseRoutes(param),
+ 0);
+ }
+ });
+ srcServer.mb.putProtocol(protocol);
+ retryPolicy.setEnabled(false);
+ assertTrue(srcSession.send(createMessage("msg"),
+ Route.parse("[Custom:[Custom:dst/session],[Custom:dst/unknown]]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList("[NO_ADDRESS_FOR_SERVICE @ localhost]",
+ "-[NO_ADDRESS_FOR_SERVICE @ localhost]",
+ "Sending message",
+ "-Sending message"),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatSetReplyWorks() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Select", new CustomPolicyFactory(true, ErrorCode.APP_FATAL_ERROR));
+ protocol.addPolicyFactory("SetReply", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new SetReplyPolicy(true, Arrays.asList(ErrorCode.APP_FATAL_ERROR), param);
+ }
+ });
+ srcServer.mb.putProtocol(protocol);
+ retryPolicy.setEnabled(false);
+ assertTrue(
+ srcSession.send(createMessage("msg"), Route.parse("[Select:[SetReply:foo],dst/session]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.APP_FATAL_ERROR, reply.getError(0).getCode());
+ assertEquals("foo", reply.getError(0).getMessage());
+ }
+
+ @Test
+ public void requireThatReplyCanBeReusedOnRetry() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("ReuseReply", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new ReuseReplyPolicy(false,
+ Arrays.asList(ErrorCode.APP_FATAL_ERROR),
+ CustomPolicyFactory.parseRoutes(param));
+ }
+ });
+ protocol.addPolicyFactory("SetReply", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new SetReplyPolicy(false,
+ Arrays.asList(ErrorCode.APP_FATAL_ERROR),
+ param);
+ }
+ });
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"),
+ Route.parse("[ReuseReply:[SetReply:foo],dst/session]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_TRANSIENT_ERROR, "dst"));
+ dstSession.reply(reply);
+ assertNotNull(msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60));
+ dstSession.acknowledge(msg);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ }
+
+ @Test
+ public void requireThatReplyCanBeRemovedAndRetried() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("RemoveReply", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new RemoveReplyPolicy(false,
+ Arrays.asList(ErrorCode.APP_TRANSIENT_ERROR),
+ CustomPolicyFactory.parseRoutes(param),
+ 0);
+ }
+ });
+ protocol.addPolicyFactory("SetReply", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new SetReplyPolicy(false,
+ Arrays.asList(ErrorCode.APP_TRANSIENT_ERROR, ErrorCode.APP_FATAL_ERROR),
+ param);
+ }
+ });
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession
+ .send(createMessage("msg"), Route.parse("[RemoveReply:[SetReply:foo],dst/session]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.APP_FATAL_ERROR, reply.getError(0).getCode());
+ assertEquals("foo", reply.getError(0).getMessage());
+ assertTrace(Arrays.asList("Resolving '[SetReply:foo]'.",
+ "Resolving 'dst/session'.",
+ "Resender resending message.",
+ "Resolving 'dst/session'.",
+ "Resolving '[SetReply:foo]'."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatIgnoreResultWorks() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("?dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR, "dst"));
+ dstSession.reply(reply);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList("Not waiting for a reply from 'dst/session'."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatIgnoreResultCanBeSetInHopBlueprint() {
+ srcServer.setupRouting(new RoutingTableSpec(SimpleProtocol.NAME)
+ .addHop(new HopSpec("foo", "dst/session").setIgnoreResult(true)));
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("foo")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Reply reply = new EmptyReply();
+ reply.swapState(msg);
+ reply.addError(new Error(ErrorCode.APP_FATAL_ERROR, "dst"));
+ dstSession.reply(reply);
+ assertNotNull(reply = ((Receptor)srcSession.getReplyHandler()).getReply(60));
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList("Not waiting for a reply from 'dst/session'."),
+ reply.getTrace());
+ }
+
+ @Test
+ public void requireThatIgnoreFlagPersistsThroughHopLookup() {
+ setupRouting(new RoutingTableSpec(SimpleProtocol.NAME).addHop(new HopSpec("foo", "dst/unknown")));
+ assertSend("?foo");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatIgnoreFlagPersistsThroughRouteLookup() {
+ setupRouting(new RoutingTableSpec(SimpleProtocol.NAME).addRoute(new RouteSpec("foo").addHop("dst/unknown")));
+ assertSend("?foo");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatIgnoreFlagPersistsThroughPolicySelect() {
+ setupPolicy("Custom", MyPolicy.newSelectAndMerge("dst/unknown"));
+ assertSend("?[Custom]");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatIgnoreFlagIsSerializedWithMessage() {
+ assertSend("dst/session foo ?bar");
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ Route route = msg.getRoute();
+ assertEquals(2, route.getNumHops());
+ Hop hop = route.getHop(0);
+ assertEquals("foo", hop.toString());
+ assertFalse(hop.getIgnoreResult());
+ hop = route.getHop(1);
+ assertEquals("?bar", hop.toString());
+ assertTrue(hop.getIgnoreResult());
+ dstSession.acknowledge(msg);
+ assertTrace("-Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatIgnoreFlagDoesNotInterfere() {
+ setupPolicy("Custom", MyPolicy.newSelectAndMerge("dst/session"));
+ assertSend("?[Custom]");
+ assertTrace("-Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatEmptySelectionCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newEmptySelection());
+ assertSend("?[Custom]");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatSelectErrorCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newSelectError(ErrorCode.APP_FATAL_ERROR, "foo"));
+ assertSend("?[Custom]");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatSelectExceptionCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newSelectException(new RuntimeException()));
+ assertSend("?[Custom]");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatSelectAndThrowCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newSelectAndThrow("dst/session", new RuntimeException()));
+ assertSend("?[Custom]");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatEmptyMergeCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newEmptyMerge("dst/session"));
+ assertSend("?[Custom]");
+ assertAcknowledge();
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatMergeErrorCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newMergeError("dst/session", ErrorCode.APP_FATAL_ERROR, "foo"));
+ assertSend("?[Custom]");
+ assertAcknowledge();
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatMergeExceptionCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newMergeException("dst/session", new RuntimeException()));
+ assertSend("?[Custom]");
+ assertAcknowledge();
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatMergeAndThrowCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newMergeAndThrow("dst/session", new RuntimeException()));
+ assertSend("?[Custom]");
+ assertAcknowledge();
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatAllocServiceAddressCanBeIgnored() {
+ assertSend("?dst/unknown");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatDepthLimitCanBeIgnored() {
+ setupPolicy("Custom", MyPolicy.newSelectAndMerge("[Custom]"));
+ assertSend("?[Custom]");
+ assertTrace("Ignoring errors in reply.");
+ }
+
+ @Test
+ public void requireThatRouteCanBeEmptyInDestination() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/session")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ assertNull(msg.getRoute());
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ }
+
+ @Test
+ public void requireThatOnlyActiveNodesAreAborted() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new CustomPolicyFactory(false));
+ protocol.addPolicyFactory("SetReply", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new SetReplyPolicy(false,
+ Arrays.asList(ErrorCode.APP_TRANSIENT_ERROR,
+ ErrorCode.APP_TRANSIENT_ERROR,
+ ErrorCode.APP_FATAL_ERROR),
+ param);
+ }
+ });
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"),
+ Route.parse("[Custom:[SetReply:foo],?bar,dst/session]")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(2, reply.getNumErrors());
+ assertEquals(ErrorCode.APP_FATAL_ERROR, reply.getError(0).getCode());
+ assertEquals(ErrorCode.SEND_ABORTED, reply.getError(1).getCode());
+ }
+
+ @Test
+ public void requireThatTimeoutWorks() {
+ retryPolicy.setBaseDelay(0.01);
+ srcSession.setTimeout(0.5);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("dst/unknown")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(2, reply.getNumErrors());
+ assertEquals(ErrorCode.NO_ADDRESS_FOR_SERVICE, reply.getError(0).getCode());
+ assertEquals(ErrorCode.TIMEOUT, reply.getError(1).getCode());
+ }
+
+ @Test
+ public void requireThatUnknownPolicyIsCaught() {
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Unknown]")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.UNKNOWN_POLICY, reply.getError(0).getCode());
+ }
+
+ private SimpleProtocol.PolicyFactory exceptionOnSelectThrowingMockFactory() {
+ return new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new RoutingPolicy() {
+
+ @Override
+ public void select(RoutingContext context) {
+ throw new RuntimeException("69");
+ }
+
+ @Override
+ public void merge(RoutingContext context) {
+ }
+
+ @Override
+ public void destroy() {
+ }
+ };
+ }
+ };
+ }
+
+ @Test
+ public void requireThatSelectExceptionIsCaught() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", exceptionOnSelectThrowingMockFactory());
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom]")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.POLICY_ERROR, reply.getError(0).getCode());
+ assertTrue(reply.getError(0).getMessage().contains("69"));
+ }
+
+ @Test
+ public void selectExceptionIncludesStackTraceInMessage() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", exceptionOnSelectThrowingMockFactory());
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom]")).isAccepted());
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertEquals(ErrorCode.POLICY_ERROR, reply.getError(0).getCode());
+ // Attempting any sort of full matching of the stack trace is brittle, so
+ // simplify by assuming any message which mentions the source file of the
+ // originating exception is good to go.
+ assertTrue(reply.getError(0).getMessage().contains("RoutingTestCase"));
+ }
+
+ @Test
+ public void requireThatMergeExceptionIsCaught() {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("Custom", new SimpleProtocol.PolicyFactory() {
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new RoutingPolicy() {
+
+ @Override
+ public void select(RoutingContext context) {
+ context.addChild(Route.parse("dst/session"));
+ }
+
+ @Override
+ public void merge(RoutingContext context) {
+ throw new RuntimeException("69");
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ };
+ }
+ });
+ srcServer.mb.putProtocol(protocol);
+ assertTrue(srcSession.send(createMessage("msg"), Route.parse("[Custom]")).isAccepted());
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertEquals(1, reply.getNumErrors());
+ assertEquals(ErrorCode.POLICY_ERROR, reply.getError(0).getCode());
+ assertTrue(reply.getError(0).getMessage().contains("69"));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Utilities
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ private static Message createMessage(String msg) {
+ SimpleMessage ret = new SimpleMessage(msg);
+ ret.getTrace().setLevel(9);
+ return ret;
+ }
+
+ private void setupRouting(RoutingTableSpec spec) {
+ srcServer.setupRouting(spec);
+ }
+
+ private void setupPolicy(String policyName, SimpleProtocol.PolicyFactory policyFactory) {
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory(policyName, policyFactory);
+ srcServer.mb.putProtocol(protocol);
+ }
+
+ private void assertSend(String route) {
+ assertTrue(srcSession.send(createMessage("msg").setRoute(Route.parse(route))).isAccepted());
+ }
+
+ private void assertAcknowledge() {
+ Message msg = ((Receptor)dstSession.getMessageHandler()).getMessage(60);
+ assertNotNull(msg);
+ dstSession.acknowledge(msg);
+ }
+
+ private void assertTrace(String... expectedTrace) {
+ Reply reply = ((Receptor)srcSession.getReplyHandler()).getReply(60);
+ assertNotNull(reply);
+ System.out.println(reply.getTrace());
+ assertFalse(reply.hasErrors());
+ assertTrace(Arrays.asList(expectedTrace), reply.getTrace());
+ }
+
+ public static void assertTrace(List<String> expected, Trace trace) {
+ String actual = trace.toString();
+ for (int i = 0, pos = -1; i < expected.size(); ++i) {
+ String line = expected.get(i).replaceFirst("\\$\\{VERSION\\}", Vtag.currentVersion.toString());
+ if (line.charAt(0) == '-') {
+ String str = line.substring(1);
+ assertTrue("Line " + i + " '" + str + "' not expected.",
+ actual.indexOf(str, pos + 1) < 0);
+ } else {
+ pos = actual.indexOf(line, pos + 1);
+ assertTrue("Line " + i + " '" + line + "' missing.", pos >= 0);
+ }
+ }
+ }
+
+ private static class RemoveReplyPolicy extends CustomPolicy {
+
+ final int idxRemove;
+
+ public RemoveReplyPolicy(boolean selectOnRetry, List<Integer> consumableErrors, List<Route> routes,
+ int idxRemove) {
+ super(selectOnRetry, consumableErrors, routes);
+ this.idxRemove = idxRemove;
+ }
+
+ public void merge(RoutingContext ctx) {
+ ctx.setReply(ctx.getChildIterator().skip(idxRemove).removeReply());
+ }
+
+ @Override
+ public void destroy() {
+ }
+ }
+
+ private static class ReuseReplyPolicy extends CustomPolicy {
+
+ final List<Integer> errorMask = new ArrayList<>();
+
+ public ReuseReplyPolicy(boolean selectOnRetry, List<Integer> errorMask,
+ List<Route> routes) {
+ super(selectOnRetry, errorMask, routes);
+ this.errorMask.addAll(errorMask);
+ }
+
+ public void merge(RoutingContext ctx) {
+ Reply ret = new EmptyReply();
+ int idx = 0;
+ int idxFirstOk = -1;
+ for (RoutingNodeIterator it = ctx.getChildIterator();
+ it.isValid(); it.next(), ++idx) {
+ Reply ref = it.getReplyRef();
+ if (!ref.hasErrors()) {
+ if (idxFirstOk < 0) {
+ idxFirstOk = idx;
+ }
+ } else {
+ for (int i = 0; i < ref.getNumErrors(); ++i) {
+ Error err = ref.getError(i);
+ if (!errorMask.contains(err.getCode())) {
+ ret.addError(err);
+ }
+ }
+ }
+ }
+ if (ret.hasErrors()) {
+ ctx.setReply(ret);
+ } else {
+ ctx.setReply(ctx.getChildIterator().skip(idxFirstOk).removeReply());
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+ }
+
+ private static class SetReplyPolicy implements RoutingPolicy {
+
+ final boolean selectOnRetry;
+ final List<Integer> errors = new ArrayList<>();
+ final String param;
+ int idx = 0;
+
+ public SetReplyPolicy(boolean selectOnRetry, List<Integer> errors, String param) {
+ this.selectOnRetry = selectOnRetry;
+ this.errors.addAll(errors);
+ this.param = param;
+ }
+
+ public void select(RoutingContext ctx) {
+ int idx = this.idx++;
+ int err = errors.get(idx < errors.size() ? idx : errors.size() - 1);
+ if (err != ErrorCode.NONE) {
+ ctx.setError(err, param);
+ } else {
+ ctx.setReply(new EmptyReply());
+ }
+ ctx.setSelectOnRetry(selectOnRetry);
+ }
+
+ public void merge(RoutingContext ctx) {
+ Reply reply = new EmptyReply();
+ reply.addError(new Error(ErrorCode.FATAL_ERROR,
+ "Merge should not be called when select() sets a reply."));
+ ctx.setReply(reply);
+ }
+
+ public void destroy() {
+ }
+ }
+
+ private static class MyPolicy implements SimpleProtocol.PolicyFactory {
+
+ final Route selectRoute;
+ final Reply selectReply;
+ final Reply mergeReply;
+ final RuntimeException selectException;
+ final RuntimeException mergeException;
+ final boolean mergeFromChild;
+
+ MyPolicy(Route selectRoute, Reply selectReply, RuntimeException selectException,
+ Reply mergeReply, RuntimeException mergeException, boolean mergeFromChild) {
+ this.selectRoute = selectRoute;
+ this.selectReply = selectReply;
+ this.selectException = selectException;
+ this.mergeReply = mergeReply;
+ this.mergeException = mergeException;
+ this.mergeFromChild = mergeFromChild;
+ }
+
+ @Override
+ public RoutingPolicy create(String param) {
+ return new RoutingPolicy() {
+
+ @Override
+ public void select(RoutingContext context) {
+ if (selectRoute != null) {
+ context.addChild(selectRoute);
+ }
+ if (selectReply != null) {
+ context.setReply(selectReply);
+ }
+ if (selectException != null) {
+ throw selectException;
+ }
+ }
+
+ @Override
+ public void merge(RoutingContext context) {
+ if (mergeReply != null) {
+ context.setReply(mergeReply);
+ } else if (mergeFromChild) {
+ context.setReply(context.getChildIterator().removeReply());
+ }
+ if (mergeException != null) {
+ throw mergeException;
+ }
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ };
+ }
+
+ static Reply newErrorReply(int errCode, String errMessage) {
+ Reply reply = new EmptyReply();
+ reply.addError(new Error(errCode, errMessage));
+ return reply;
+ }
+
+ static MyPolicy newSelectAndMerge(String select) {
+ return new MyPolicy(Route.parse(select), null, null, null, null, true);
+ }
+
+ static MyPolicy newEmptySelection() {
+ return new MyPolicy(null, null, null, null, null, false);
+ }
+
+ static MyPolicy newSelectError(int errCode, String errMessage) {
+ return new MyPolicy(null, newErrorReply(errCode, errMessage), null, null, null, false);
+ }
+
+ static MyPolicy newSelectException(RuntimeException e) {
+ return new MyPolicy(null, null, e, null, null, false);
+ }
+
+ static MyPolicy newSelectAndThrow(String select, RuntimeException e) {
+ return new MyPolicy(Route.parse(select), null, e, null, null, false);
+ }
+
+ static MyPolicy newEmptyMerge(String select) {
+ return new MyPolicy(Route.parse(select), null, null, null, null, false);
+ }
+
+ static MyPolicy newMergeError(String select, int errCode, String errMessage) {
+ return new MyPolicy(Route.parse(select), null, null, newErrorReply(errCode, errMessage), null, false);
+ }
+
+ static MyPolicy newMergeException(String select, RuntimeException e) {
+ return new MyPolicy(Route.parse(select), null, null, null, e, false);
+ }
+
+ static MyPolicy newMergeAndThrow(String select, RuntimeException e) {
+ return new MyPolicy(Route.parse(select), null, null, null, e, true);
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/test/QueueAdapterTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/test/QueueAdapterTestCase.java
new file mode 100644
index 00000000000..000505f6349
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/test/QueueAdapterTestCase.java
@@ -0,0 +1,108 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.messagebus.Message;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.text.Utf8String;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class QueueAdapterTestCase {
+
+ private static final int NO_WAIT = 0;
+ private static final int WAIT_FOREVER = 60;
+
+ @Test
+ public void requireThatAccessorsWork() {
+ QueueAdapter queue = new QueueAdapter();
+ assertTrue(queue.isEmpty());
+ assertEquals(0, queue.size());
+
+ Message msg = new MyMessage();
+ queue.handleMessage(msg);
+ assertFalse(queue.isEmpty());
+ assertEquals(1, queue.size());
+
+ MyReply reply = new MyReply();
+ queue.handleReply(reply);
+ assertFalse(queue.isEmpty());
+ assertEquals(2, queue.size());
+
+ assertSame(msg, queue.dequeue());
+ assertSame(reply, queue.dequeue());
+ }
+
+ @Test
+ public void requireThatSizeCanBeWaitedFor() {
+ final QueueAdapter queue = new QueueAdapter();
+ assertTrue(queue.waitSize(0, NO_WAIT));
+ assertFalse(queue.waitSize(1, NO_WAIT));
+ queue.handleMessage(new MyMessage());
+ assertFalse(queue.waitSize(0, NO_WAIT));
+ assertTrue(queue.waitSize(1, NO_WAIT));
+
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(100);
+ queue.handleMessage(new MyMessage());
+ } catch (InterruptedException e) {
+
+ }
+ }
+ };
+ thread.start();
+ assertTrue(queue.waitSize(2, WAIT_FOREVER));
+ }
+
+ @Test
+ public void requireThatWaitCanBeInterrupted() throws InterruptedException {
+ final QueueAdapter queue = new QueueAdapter();
+ final AtomicReference<Boolean> result = new AtomicReference<>();
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ result.set(queue.waitSize(1, WAIT_FOREVER));
+ }
+ };
+ thread.start();
+ thread.interrupt();
+ thread.join();
+ assertEquals(Boolean.FALSE, result.get());
+ }
+
+ private static class MyMessage extends Message {
+
+ @Override
+ public Utf8String getProtocol() {
+ return null;
+ }
+
+ @Override
+ public int getType() {
+ return 0;
+ }
+ }
+
+ private static class MyReply extends Reply {
+
+ @Override
+ public Utf8String getProtocol() {
+ return null;
+ }
+
+ @Override
+ public int getType() {
+ return 0;
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/test/ReceptorTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/test/ReceptorTestCase.java
new file mode 100644
index 00000000000..a34f37b0196
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/test/ReceptorTestCase.java
@@ -0,0 +1,143 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.messagebus.Message;
+import com.yahoo.messagebus.Reply;
+import com.yahoo.text.Utf8String;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ReceptorTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Receptor receptor = new Receptor();
+ assertNull(receptor.getMessage(0));
+ Message msg = new MyMessage();
+ receptor.handleMessage(msg);
+ assertSame(msg, receptor.getMessage(0));
+
+ Reply reply = new MyReply();
+ receptor.handleReply(reply);
+ assertSame(reply, receptor.getReply(0));
+ }
+
+ @Test
+ public void requireThatMessagesAndRepliesAreTrackedIndividually() {
+ Receptor receptor = new Receptor();
+ receptor.handleMessage(new MyMessage());
+ receptor.handleReply(new MyReply());
+ assertNotNull(receptor.getMessage(0));
+ assertNotNull(receptor.getReply(0));
+
+ receptor.handleMessage(new MyMessage());
+ receptor.handleReply(new MyReply());
+ assertNotNull(receptor.getReply(0));
+ assertNotNull(receptor.getMessage(0));
+ }
+
+ @Test
+ public void requireThatMessagesCanBeWaitedFor() {
+ final Receptor receptor = new Receptor();
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(100);
+ receptor.handleMessage(new MyMessage());
+ } catch (InterruptedException e) {
+
+ }
+ }
+ };
+ thread.start();
+ assertNotNull(receptor.getMessage(60));
+ }
+
+ @Test
+ public void requireThatMessageWaitCanBeInterrupted() throws InterruptedException {
+ final Receptor receptor = new Receptor();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ receptor.getMessage(60);
+ latch.countDown();
+ }
+ };
+ thread.start();
+ thread.interrupt();
+ assertTrue(latch.await(30, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatRepliesCanBeWaitedFor() {
+ final Receptor receptor = new Receptor();
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(100);
+ receptor.handleReply(new MyReply());
+ } catch (InterruptedException e) {
+
+ }
+ }
+ };
+ thread.start();
+ assertNotNull(receptor.getReply(60));
+ }
+
+ @Test
+ public void requireThatReplyWaitCanBeInterrupted() throws InterruptedException {
+ final Receptor receptor = new Receptor();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ receptor.getReply(60);
+ latch.countDown();
+ }
+ };
+ thread.start();
+ thread.interrupt();
+ assertTrue(latch.await(30, TimeUnit.SECONDS));
+ }
+
+ private static class MyMessage extends Message {
+
+ @Override
+ public Utf8String getProtocol() {
+ return null;
+ }
+
+ @Override
+ public int getType() {
+ return 0;
+ }
+ }
+
+ private static class MyReply extends Reply {
+
+ @Override
+ public Utf8String getProtocol() {
+ return null;
+ }
+
+ @Override
+ public int getType() {
+ return 0;
+ }
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleMessageTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleMessageTestCase.java
new file mode 100644
index 00000000000..ead0fdb88b4
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleMessageTestCase.java
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class SimpleMessageTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ SimpleMessage msg = new SimpleMessage("foo");
+ assertEquals(SimpleProtocol.MESSAGE, msg.getType());
+ assertEquals(SimpleProtocol.NAME, msg.getProtocol());
+ assertEquals(3, msg.getApproxSize());
+ assertEquals("foo", msg.getValue());
+ msg.setValue("bar");
+ assertEquals("bar", msg.getValue());
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleProtocolTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleProtocolTestCase.java
new file mode 100644
index 00000000000..ce42762f235
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleProtocolTestCase.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import com.yahoo.component.Version;
+import com.yahoo.messagebus.EmptyReply;
+import com.yahoo.messagebus.Routable;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class SimpleProtocolTestCase {
+
+ private static final Version VERSION = new Version(1);
+ private static final SimpleProtocol PROTOCOL = new SimpleProtocol();
+
+ @Test
+ public void requireThatNameIsSet() {
+ assertEquals(SimpleProtocol.NAME, PROTOCOL.getName());
+ }
+
+ @Test
+ public void requireThatMetricSetIsNull() {
+ assertNull(PROTOCOL.getMetrics());
+ }
+
+ @Test
+ public void requireThatMessageCanBeEncodedAndDecoded() {
+ SimpleMessage msg = new SimpleMessage("foo");
+ byte[] buf = PROTOCOL.encode(Version.emptyVersion, msg);
+ Routable routable = PROTOCOL.decode(Version.emptyVersion, buf);
+ assertNotNull(routable);
+ assertEquals(SimpleMessage.class, routable.getClass());
+ msg = (SimpleMessage)routable;
+ assertEquals("foo", msg.getValue());
+ }
+
+ @Test
+ public void requireThatReplyCanBeDecoded() {
+ SimpleReply reply = new SimpleReply("foo");
+ byte[] buf = PROTOCOL.encode(Version.emptyVersion, reply);
+ Routable routable = PROTOCOL.decode(Version.emptyVersion, buf);
+ assertNotNull(routable);
+ assertEquals(SimpleReply.class, routable.getClass());
+ reply = (SimpleReply)routable;
+ assertEquals("foo", reply.getValue());
+ }
+
+ @Test
+ public void requireThatUnknownRoutablesAreNotEncoded() {
+ assertNull(PROTOCOL.encode(VERSION, new EmptyReply()));
+ }
+
+ @Test
+ public void requireThatEmptyBufferIsNotDecoded() {
+ assertNull(PROTOCOL.decode(VERSION, new byte[0]));
+ }
+
+ @Test
+ public void requireThatUnknownBufferIsNotDecoded() {
+ assertNull(PROTOCOL.decode(VERSION, new byte[] { 'U' }));
+ }
+}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleReplyTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleReplyTestCase.java
new file mode 100644
index 00000000000..474fea14ac7
--- /dev/null
+++ b/messagebus/src/test/java/com/yahoo/messagebus/test/SimpleReplyTestCase.java
@@ -0,0 +1,22 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.messagebus.test;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class SimpleReplyTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ SimpleReply reply = new SimpleReply("foo");
+ assertEquals(SimpleProtocol.REPLY, reply.getType());
+ assertEquals(SimpleProtocol.NAME, reply.getProtocol());
+ assertEquals("foo", reply.getValue());
+ reply.setValue("bar");
+ assertEquals("bar", reply.getValue());
+ }
+}
diff --git a/messagebus/src/testlist.txt b/messagebus/src/testlist.txt
new file mode 100644
index 00000000000..e096aa5a9a1
--- /dev/null
+++ b/messagebus/src/testlist.txt
@@ -0,0 +1,41 @@
+tests/advancedrouting
+tests/auto-reply
+tests/bucketsequence
+tests/blob
+tests/choke
+tests/configagent
+tests/context
+tests/emptyreply
+tests/error
+tests/identity
+tests/loadbalance
+tests/messagebus
+tests/messenger
+tests/oos
+tests/protocolrepository
+tests/queue
+tests/replygate
+tests/resender
+tests/result
+tests/retrypolicy
+tests/routable
+tests/routablequeue
+tests/routeparser
+tests/routing
+tests/routingcontext
+tests/routingspec
+tests/rpcserviceaddress
+tests/sendadapter
+tests/sequencer
+tests/serviceaddress
+tests/servicepool
+tests/shutdown
+tests/simple-roundtrip
+tests/simpleprotocol
+tests/slobrok
+tests/sourcesession
+tests/targetpool
+tests/throttling
+tests/timeout
+tests/trace-roundtrip
+tests/messageordering
diff --git a/messagebus/src/testrun/.gitignore b/messagebus/src/testrun/.gitignore
new file mode 100644
index 00000000000..b29b0c6486c
--- /dev/null
+++ b/messagebus/src/testrun/.gitignore
@@ -0,0 +1,9 @@
+test-report.html
+test-report.html.*
+test.*.*.desc
+test.*.*.file.*
+test.*.*.files.html
+test.*.*.log
+tmp.*
+xsync.log
+/test.*.*.result
diff --git a/messagebus/src/tests/.gitignore b/messagebus/src/tests/.gitignore
new file mode 100644
index 00000000000..c473b24b98a
--- /dev/null
+++ b/messagebus/src/tests/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+testrunner
+*_test
diff --git a/messagebus/src/tests/CMakeLists.txt b/messagebus/src/tests/CMakeLists.txt
new file mode 100644
index 00000000000..9991e8a8e8c
--- /dev/null
+++ b/messagebus/src/tests/CMakeLists.txt
@@ -0,0 +1,41 @@
+add_subdirectory(advancedrouting)
+add_subdirectory(auto-reply)
+add_subdirectory(blob)
+add_subdirectory(bucketsequence)
+add_subdirectory(choke)
+add_subdirectory(configagent)
+add_subdirectory(context)
+add_subdirectory(emptyreply)
+add_subdirectory(error)
+add_subdirectory(identity)
+add_subdirectory(loadbalance)
+add_subdirectory(messagebus)
+add_subdirectory(messageordering)
+add_subdirectory(messenger)
+add_subdirectory(oos)
+add_subdirectory(protocolrepository)
+add_subdirectory(queue)
+add_subdirectory(replygate)
+add_subdirectory(resender)
+add_subdirectory(result)
+add_subdirectory(retrypolicy)
+add_subdirectory(routable)
+add_subdirectory(routablequeue)
+add_subdirectory(routeparser)
+add_subdirectory(routing)
+add_subdirectory(routingcontext)
+add_subdirectory(routingspec)
+add_subdirectory(rpcserviceaddress)
+add_subdirectory(sendadapter)
+add_subdirectory(sequencer)
+add_subdirectory(serviceaddress)
+add_subdirectory(servicepool)
+add_subdirectory(shutdown)
+add_subdirectory(simple-roundtrip)
+add_subdirectory(simpleprotocol)
+add_subdirectory(slobrok)
+add_subdirectory(sourcesession)
+add_subdirectory(targetpool)
+add_subdirectory(throttling)
+add_subdirectory(timeout)
+add_subdirectory(trace-roundtrip)
diff --git a/messagebus/src/tests/advancedrouting/.gitignore b/messagebus/src/tests/advancedrouting/.gitignore
new file mode 100644
index 00000000000..570db59096d
--- /dev/null
+++ b/messagebus/src/tests/advancedrouting/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+advancedrouting_test
+messagebus_advancedrouting_test_app
diff --git a/messagebus/src/tests/advancedrouting/CMakeLists.txt b/messagebus/src/tests/advancedrouting/CMakeLists.txt
new file mode 100644
index 00000000000..99f5b037b69
--- /dev/null
+++ b/messagebus/src/tests/advancedrouting/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_advancedrouting_test_app
+ SOURCES
+ advancedrouting.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_advancedrouting_test_app COMMAND messagebus_advancedrouting_test_app)
diff --git a/messagebus/src/tests/advancedrouting/DESC b/messagebus/src/tests/advancedrouting/DESC
new file mode 100644
index 00000000000..735d63dcdc3
--- /dev/null
+++ b/messagebus/src/tests/advancedrouting/DESC
@@ -0,0 +1 @@
+advancedrouting test. Take a look at advancedrouting.cpp for details.
diff --git a/messagebus/src/tests/advancedrouting/FILES b/messagebus/src/tests/advancedrouting/FILES
new file mode 100644
index 00000000000..61eb026ac3a
--- /dev/null
+++ b/messagebus/src/tests/advancedrouting/FILES
@@ -0,0 +1 @@
+advancedrouting.cpp
diff --git a/messagebus/src/tests/advancedrouting/advancedrouting.cpp b/messagebus/src/tests/advancedrouting/advancedrouting.cpp
new file mode 100644
index 00000000000..976746b0738
--- /dev/null
+++ b/messagebus/src/tests/advancedrouting/advancedrouting.cpp
@@ -0,0 +1,188 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routing_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routing/errordirective.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/testlib/custompolicy.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+
+using namespace mbus;
+
+class TestData {
+public:
+ Slobrok _slobrok;
+ RetryTransientErrorsPolicy::SP _retryPolicy;
+ TestServer _srcServer;
+ SourceSession::UP _srcSession;
+ Receptor _srcHandler;
+ TestServer _dstServer;
+ DestinationSession::UP _fooSession;
+ Receptor _fooHandler;
+ DestinationSession::UP _barSession;
+ Receptor _barHandler;
+ DestinationSession::UP _bazSession;
+ Receptor _bazHandler;
+
+public:
+ TestData();
+ bool start();
+};
+
+class Test : public vespalib::TestApp {
+private:
+ Message::UP createMessage(const string &msg);
+ bool testTrace(const std::vector<string> &expected, const Trace &trace);
+
+public:
+ int Main();
+ void testAdvanced(TestData &data);
+};
+
+TEST_APPHOOK(Test);
+
+TestData::TestData() :
+ _slobrok(),
+ _retryPolicy(new RetryTransientErrorsPolicy()),
+ _srcServer(MessageBusParams().setRetryPolicy(_retryPolicy).addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setSlobrokConfig(_slobrok.config())),
+ _srcSession(),
+ _srcHandler(),
+ _dstServer(MessageBusParams().addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setIdentity(Identity("dst")).setSlobrokConfig(_slobrok.config())),
+ _fooSession(),
+ _fooHandler(),
+ _barSession(),
+ _barHandler(),
+ _bazSession(),
+ _bazHandler()
+{
+ _retryPolicy->setBaseDelay(0);
+}
+
+bool
+TestData::start()
+{
+ _srcSession = _srcServer.mb.createSourceSession(SourceSessionParams().setReplyHandler(_srcHandler));
+ if (_srcSession.get() == NULL) {
+ LOG(error, "Could not create source session.");
+ return false;
+ }
+ _fooSession = _dstServer.mb.createDestinationSession(DestinationSessionParams().setName("foo").setMessageHandler(_fooHandler));
+ if (_fooSession.get() == NULL) {
+ LOG(error, "Could not create foo session.");
+ return false;
+ }
+ _barSession = _dstServer.mb.createDestinationSession(DestinationSessionParams().setName("bar").setMessageHandler(_barHandler));
+ if (_barSession.get() == NULL) {
+ LOG(error, "Could not create bar session.");
+ return false;
+ }
+ _bazSession = _dstServer.mb.createDestinationSession(DestinationSessionParams().setName("baz").setMessageHandler(_bazHandler));
+ if (_bazSession.get() == NULL) {
+ LOG(error, "Could not create baz session.");
+ return false;
+ }
+ if (!_srcServer.waitSlobrok("dst/*", 3u)) {
+ return false;
+ }
+ return true;
+}
+
+Message::UP
+Test::createMessage(const string &msg)
+{
+ Message::UP ret(new SimpleMessage(msg));
+ ret->getTrace().setLevel(9);
+ return ret;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("routing_test");
+
+ TestData data;
+ ASSERT_TRUE(data.start());
+
+ testAdvanced(data); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+void
+Test::testAdvanced(TestData &data)
+{
+ const double TIMEOUT=60;
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(false, ErrorCode::NO_ADDRESS_FOR_SERVICE)));
+ data._srcServer.mb.putProtocol(protocol);
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addHop(HopSpec("bar", "dst/bar"))
+ .addHop(HopSpec("baz", "dst/baz"))
+ .addRoute(RouteSpec("baz").addHop("baz"))));
+ string route = vespalib::make_vespa_string("[Custom:%s,bar,route:baz,dst/cox,?dst/unknown]",
+ data._fooSession->getConnectionSpec().c_str());
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse(route)).isAccepted());
+
+ // Initial send.
+ Message::UP msg = data._fooHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._fooSession->acknowledge(std::move(msg));
+ msg = data._barHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::TRANSIENT_ERROR, "bar"));
+ data._barSession->reply(std::move(reply));
+ msg = data._bazHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::TRANSIENT_ERROR, "baz1"));
+ data._bazSession->reply(std::move(reply));
+
+ // First retry.
+ msg = data._fooHandler.getMessage(0);
+ ASSERT_TRUE(msg.get() == NULL);
+ msg = data._barHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._barSession->acknowledge(std::move(msg));
+ msg = data._bazHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::TRANSIENT_ERROR, "baz2"));
+ data._bazSession->reply(std::move(reply));
+
+ // Second retry.
+ msg = data._fooHandler.getMessage(0);
+ ASSERT_TRUE(msg.get() == NULL);
+ msg = data._barHandler.getMessage(0);
+ ASSERT_TRUE(msg.get() == NULL);
+ msg = data._bazHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::FATAL_ERROR, "baz3"));
+ data._bazSession->reply(std::move(reply));
+
+ // Done.
+ reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(2u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::FATAL_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL((uint32_t)ErrorCode::NO_ADDRESS_FOR_SERVICE, reply->getError(1).getCode());
+}
diff --git a/messagebus/src/tests/auto-reply/.gitignore b/messagebus/src/tests/auto-reply/.gitignore
new file mode 100644
index 00000000000..061d2f262bd
--- /dev/null
+++ b/messagebus/src/tests/auto-reply/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+auto-reply_test
+messagebus_auto-reply_test_app
diff --git a/messagebus/src/tests/auto-reply/CMakeLists.txt b/messagebus/src/tests/auto-reply/CMakeLists.txt
new file mode 100644
index 00000000000..950ade550b7
--- /dev/null
+++ b/messagebus/src/tests/auto-reply/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_auto-reply_test_app
+ SOURCES
+ auto-reply.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_auto-reply_test_app COMMAND messagebus_auto-reply_test_app)
diff --git a/messagebus/src/tests/auto-reply/DESC b/messagebus/src/tests/auto-reply/DESC
new file mode 100644
index 00000000000..2aec186bfac
--- /dev/null
+++ b/messagebus/src/tests/auto-reply/DESC
@@ -0,0 +1,2 @@
+Test that a deleted Message or Reply with a non-empty call-stack will
+generate an automatic Reply.
diff --git a/messagebus/src/tests/auto-reply/FILES b/messagebus/src/tests/auto-reply/FILES
new file mode 100644
index 00000000000..29f5dbbc1bb
--- /dev/null
+++ b/messagebus/src/tests/auto-reply/FILES
@@ -0,0 +1 @@
+auto-reply.cpp
diff --git a/messagebus/src/tests/auto-reply/auto-reply.cpp b/messagebus/src/tests/auto-reply/auto-reply.cpp
new file mode 100644
index 00000000000..876755dfd0e
--- /dev/null
+++ b/messagebus/src/tests/auto-reply/auto-reply.cpp
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("auto-reply_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("auto-reply_test");
+ RoutableQueue q;
+ {
+ Message::UP msg(new SimpleMessage("test"));
+ }
+ EXPECT_TRUE(q.size() == 0);
+ {
+ Message::UP msg(new SimpleMessage("test"));
+ msg->pushHandler(q);
+ }
+ EXPECT_TRUE(q.size() == 1);
+ {
+ Reply::UP reply(new SimpleReply("test"));
+ }
+ EXPECT_TRUE(q.size() == 1);
+ {
+ Reply::UP reply(new SimpleReply("test"));
+ reply->pushHandler(q);
+ }
+ EXPECT_TRUE(q.size() == 2);
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/blob/.gitignore b/messagebus/src/tests/blob/.gitignore
new file mode 100644
index 00000000000..8602aa42ade
--- /dev/null
+++ b/messagebus/src/tests/blob/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+blob_test
+messagebus_blob_test_app
diff --git a/messagebus/src/tests/blob/CMakeLists.txt b/messagebus/src/tests/blob/CMakeLists.txt
new file mode 100644
index 00000000000..d9a865519cb
--- /dev/null
+++ b/messagebus/src/tests/blob/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_blob_test_app
+ SOURCES
+ blob.cpp
+ DEPENDS
+ messagebus
+ messagebus_messagebus-test
+)
+vespa_add_test(NAME messagebus_blob_test_app COMMAND messagebus_blob_test_app)
diff --git a/messagebus/src/tests/blob/DESC b/messagebus/src/tests/blob/DESC
new file mode 100644
index 00000000000..b2ba59c187f
--- /dev/null
+++ b/messagebus/src/tests/blob/DESC
@@ -0,0 +1 @@
+Test the Blob and BlobRef classes.
diff --git a/messagebus/src/tests/blob/FILES b/messagebus/src/tests/blob/FILES
new file mode 100644
index 00000000000..fd1396e55e3
--- /dev/null
+++ b/messagebus/src/tests/blob/FILES
@@ -0,0 +1 @@
+blob.cpp
diff --git a/messagebus/src/tests/blob/blob.cpp b/messagebus/src/tests/blob/blob.cpp
new file mode 100644
index 00000000000..9b0df1ae476
--- /dev/null
+++ b/messagebus/src/tests/blob/blob.cpp
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("blob_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/blob.h>
+#include <vespa/messagebus/blobref.h>
+
+using mbus::Blob;
+using mbus::BlobRef;
+
+TEST_SETUP(Test);
+
+Blob makeBlob(const char *txt) {
+ Blob b(strlen(txt) + 1);
+ strcpy(b.data(), txt);
+ return b;
+}
+
+BlobRef makeBlobRef(const Blob &b) {
+ return BlobRef(b.data(), b.size());
+}
+
+Blob returnBlob(Blob b) {
+ return b;
+}
+
+BlobRef returnBlobRef(BlobRef br) {
+ return br;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("blob_test");
+
+ // create a blob
+ Blob b = makeBlob("test");
+ EXPECT_TRUE(b.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", b.data()) == 0);
+
+ // create a ref to a blob
+ BlobRef br = makeBlobRef(b);
+ EXPECT_TRUE(br.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", br.data()) == 0);
+ EXPECT_TRUE(b.data() == br.data());
+
+ // non-destructive copy of ref
+ BlobRef br2 = returnBlobRef(br);
+ EXPECT_TRUE(br.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", br.data()) == 0);
+ EXPECT_TRUE(b.data() == br.data());
+ EXPECT_TRUE(br2.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", br2.data()) == 0);
+ EXPECT_TRUE(b.data() == br2.data());
+
+ br = br2;
+ EXPECT_TRUE(br.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", br.data()) == 0);
+ EXPECT_TRUE(b.data() == br.data());
+ EXPECT_TRUE(br2.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", br2.data()) == 0);
+ EXPECT_TRUE(b.data() == br2.data());
+
+ // destructive copy of blob
+ Blob b2 = returnBlob(std::move(b));
+ EXPECT_EQUAL(0u, b.size());
+ EXPECT_TRUE(b.data() == 0);
+ EXPECT_TRUE(b2.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", b2.data()) == 0);
+
+ b.swap(b2);
+ EXPECT_EQUAL(0u, b2.size());
+ EXPECT_TRUE(b2.data() == 0);
+ EXPECT_TRUE(b.size() == strlen("test") + 1);
+ EXPECT_TRUE(strcmp("test", b.data()) == 0);
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/bucketsequence/.gitignore b/messagebus/src/tests/bucketsequence/.gitignore
new file mode 100644
index 00000000000..cca77fed742
--- /dev/null
+++ b/messagebus/src/tests/bucketsequence/.gitignore
@@ -0,0 +1,3 @@
+/.depend
+/Makefile
+messagebus_bucketsequence_test_app
diff --git a/messagebus/src/tests/bucketsequence/CMakeLists.txt b/messagebus/src/tests/bucketsequence/CMakeLists.txt
new file mode 100644
index 00000000000..5ab01524d51
--- /dev/null
+++ b/messagebus/src/tests/bucketsequence/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_bucketsequence_test_app
+ SOURCES
+ bucketsequence.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_bucketsequence_test_app COMMAND messagebus_bucketsequence_test_app)
diff --git a/messagebus/src/tests/bucketsequence/DESC b/messagebus/src/tests/bucketsequence/DESC
new file mode 100644
index 00000000000..b2e8d79519b
--- /dev/null
+++ b/messagebus/src/tests/bucketsequence/DESC
@@ -0,0 +1 @@
+bucketsequence test. Take a look at bucketsequence.cpp for details.
diff --git a/messagebus/src/tests/bucketsequence/FILES b/messagebus/src/tests/bucketsequence/FILES
new file mode 100644
index 00000000000..6db6cc0a2cd
--- /dev/null
+++ b/messagebus/src/tests/bucketsequence/FILES
@@ -0,0 +1 @@
+bucketsequence.cpp
diff --git a/messagebus/src/tests/bucketsequence/bucketsequence.cpp b/messagebus/src/tests/bucketsequence/bucketsequence.cpp
new file mode 100644
index 00000000000..6036c43f659
--- /dev/null
+++ b/messagebus/src/tests/bucketsequence/bucketsequence.cpp
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("bucketsequence_test");
+
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+class MyMessage : public SimpleMessage {
+public:
+ MyMessage() : SimpleMessage("foo") { }
+ bool hasBucketSequence() { return true; }
+};
+
+int
+Test::Main()
+{
+ TEST_INIT("bucketsequence_test");
+
+ Slobrok slobrok;
+ TestServer server(MessageBusParams()
+ .addProtocol(IProtocol::SP(new SimpleProtocol()))
+ .setRetryPolicy(IRetryPolicy::SP(new RetryTransientErrorsPolicy())),
+ RPCNetworkParams()
+ .setSlobrokConfig(slobrok.config()));
+ Receptor receptor;
+ SourceSession::UP session = server.mb.createSourceSession(
+ SourceSessionParams()
+ .setReplyHandler(receptor));
+ Message::UP msg(new MyMessage());
+ msg->setRoute(Route::parse("foo"));
+ ASSERT_TRUE(session->send(std::move(msg)).isAccepted());
+ Reply::UP reply = receptor.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::SEQUENCE_ERROR, reply->getError(0).getCode());
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/choke/.gitignore b/messagebus/src/tests/choke/.gitignore
new file mode 100644
index 00000000000..320ee997500
--- /dev/null
+++ b/messagebus/src/tests/choke/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+choke_test
+messagebus_choke_test_app
diff --git a/messagebus/src/tests/choke/CMakeLists.txt b/messagebus/src/tests/choke/CMakeLists.txt
new file mode 100644
index 00000000000..02e2c14b943
--- /dev/null
+++ b/messagebus/src/tests/choke/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_choke_test_app
+ SOURCES
+ choke.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_choke_test_app NO_VALGRIND COMMAND messagebus_choke_test_app)
diff --git a/messagebus/src/tests/choke/DESC b/messagebus/src/tests/choke/DESC
new file mode 100644
index 00000000000..fd1d4965b7d
--- /dev/null
+++ b/messagebus/src/tests/choke/DESC
@@ -0,0 +1 @@
+choke test. Take a look at choke.cpp for details.
diff --git a/messagebus/src/tests/choke/FILES b/messagebus/src/tests/choke/FILES
new file mode 100644
index 00000000000..7a0d95feb52
--- /dev/null
+++ b/messagebus/src/tests/choke/FILES
@@ -0,0 +1 @@
+choke.cpp
diff --git a/messagebus/src/tests/choke/choke.cpp b/messagebus/src/tests/choke/choke.cpp
new file mode 100644
index 00000000000..567ec52db79
--- /dev/null
+++ b/messagebus/src/tests/choke/choke.cpp
@@ -0,0 +1,227 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("choke_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/reply.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class TestData {
+public:
+ Slobrok _slobrok;
+ TestServer _srcServer;
+ SourceSession::UP _srcSession;
+ Receptor _srcHandler;
+ TestServer _dstServer;
+ DestinationSession::UP _dstSession;
+ Receptor _dstHandler;
+
+public:
+ TestData();
+ bool start();
+};
+
+class Test : public vespalib::TestApp {
+private:
+ Message::UP createMessage(const string &msg);
+
+public:
+ int Main();
+ void testMaxCount(TestData &data);
+ void testMaxSize(TestData &data);
+};
+
+TEST_APPHOOK(Test);
+
+TestData::TestData() :
+ _slobrok(),
+ _srcServer(MessageBusParams()
+ .setRetryPolicy(IRetryPolicy::SP())
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setSlobrokConfig(_slobrok.config())),
+ _srcSession(),
+ _srcHandler(),
+ _dstServer(MessageBusParams()
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("dst"))
+ .setSlobrokConfig(_slobrok.config())),
+ _dstSession(),
+ _dstHandler()
+{
+ // empty
+}
+
+bool
+TestData::start()
+{
+ _srcSession = _srcServer.mb.createSourceSession(SourceSessionParams()
+ .setThrottlePolicy(IThrottlePolicy::SP())
+ .setReplyHandler(_srcHandler));
+ if (_srcSession.get() == NULL) {
+ return false;
+ }
+ _dstSession = _dstServer.mb.createDestinationSession(DestinationSessionParams()
+ .setName("session")
+ .setMessageHandler(_dstHandler));
+ if (_dstSession.get() == NULL) {
+ return false;
+ }
+ if (!_srcServer.waitSlobrok("dst/session", 1u)) {
+ return false;
+ }
+ return true;
+}
+
+Message::UP
+Test::createMessage(const string &msg)
+{
+ Message::UP ret(new SimpleMessage(msg));
+ ret->getTrace().setLevel(9);
+ return ret;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("choke_test");
+
+ TestData data;
+ ASSERT_TRUE(data.start());
+
+ testMaxCount(data); TEST_FLUSH();
+ testMaxSize(data); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+static const double TIMEOUT = 120;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test::testMaxCount(TestData &data)
+{
+ uint32_t max = 10;
+ data._dstServer.mb.setMaxPendingCount(max);
+ std::vector<Message*> lst;
+ for (uint32_t i = 0; i < max * 2; ++i) {
+ if (i < max) {
+ EXPECT_EQUAL(i, data._dstServer.mb.getPendingCount());
+ } else {
+ EXPECT_EQUAL(max, data._dstServer.mb.getPendingCount());
+ }
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ if (i < max) {
+ Message::UP msg = data._dstHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ lst.push_back(msg.release());
+ } else {
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::SESSION_BUSY, reply->getError(0).getCode());
+ }
+ }
+ for (uint32_t i = 0; i < 5; ++i) {
+ Message::UP msg(lst[0]);
+ lst.erase(lst.begin());
+ data._dstSession->acknowledge(std::move(msg));
+
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(!reply->hasErrors());
+ msg = reply->getMessage();
+ ASSERT_TRUE(msg.get() != NULL);
+ EXPECT_TRUE(data._srcSession->send(std::move(msg), Route::parse("dst/session")).isAccepted());
+
+ msg = data._dstHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ lst.push_back(msg.release());
+ }
+ while (!lst.empty()) {
+ EXPECT_EQUAL(lst.size(), data._dstServer.mb.getPendingCount());
+ Message::UP msg(lst[0]);
+ lst.erase(lst.begin());
+ data._dstSession->acknowledge(std::move(msg));
+
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(!reply->hasErrors());
+ }
+ EXPECT_EQUAL(0u, data._dstServer.mb.getPendingCount());
+}
+
+void
+Test::testMaxSize(TestData &data)
+{
+ uint32_t size = createMessage("msg")->getApproxSize();
+ uint32_t max = size * 10;
+ data._dstServer.mb.setMaxPendingSize(max);
+ std::vector<Message*> lst;
+ for (uint32_t i = 0; i < max * 2; i += size) {
+ if (i < max) {
+ EXPECT_EQUAL(i, data._dstServer.mb.getPendingSize());
+ } else {
+ EXPECT_EQUAL(max, data._dstServer.mb.getPendingSize());
+ }
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ if (i < max) {
+ Message::UP msg = data._dstHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ lst.push_back(msg.release());
+ } else {
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::SESSION_BUSY, reply->getError(0).getCode());
+ }
+ }
+ for (uint32_t i = 0; i < 5; ++i) {
+ Message::UP msg(lst[0]);
+ lst.erase(lst.begin());
+ data._dstSession->acknowledge(std::move(msg));
+
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(!reply->hasErrors());
+ msg = reply->getMessage();
+ ASSERT_TRUE(msg.get() != NULL);
+ EXPECT_TRUE(data._srcSession->send(std::move(msg), Route::parse("dst/session")).isAccepted());
+
+ msg = data._dstHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ lst.push_back(msg.release());
+ }
+ while (!lst.empty()) {
+ EXPECT_EQUAL(size * lst.size(), data._dstServer.mb.getPendingSize());
+ Message::UP msg(lst[0]);
+ lst.erase(lst.begin());
+ data._dstSession->acknowledge(std::move(msg));
+
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(!reply->hasErrors());
+ }
+ EXPECT_EQUAL(0u, data._dstServer.mb.getPendingSize());
+}
diff --git a/messagebus/src/tests/configagent/.gitignore b/messagebus/src/tests/configagent/.gitignore
new file mode 100644
index 00000000000..240f433156c
--- /dev/null
+++ b/messagebus/src/tests/configagent/.gitignore
@@ -0,0 +1,5 @@
+.depend
+Makefile
+configagent_test
+test.cfg
+messagebus_configagent_test_app
diff --git a/messagebus/src/tests/configagent/CMakeLists.txt b/messagebus/src/tests/configagent/CMakeLists.txt
new file mode 100644
index 00000000000..04170cc9d05
--- /dev/null
+++ b/messagebus/src/tests/configagent/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_configagent_test_app
+ SOURCES
+ configagent.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_configagent_test_app COMMAND messagebus_configagent_test_app)
diff --git a/messagebus/src/tests/configagent/DESC b/messagebus/src/tests/configagent/DESC
new file mode 100644
index 00000000000..b4db2789b01
--- /dev/null
+++ b/messagebus/src/tests/configagent/DESC
@@ -0,0 +1,2 @@
+Test that the config agent is able to configure a config handler using
+config files.
diff --git a/messagebus/src/tests/configagent/FILES b/messagebus/src/tests/configagent/FILES
new file mode 100644
index 00000000000..49fd8684ac1
--- /dev/null
+++ b/messagebus/src/tests/configagent/FILES
@@ -0,0 +1,3 @@
+configagent.cpp
+full.cfg
+half.cfg
diff --git a/messagebus/src/tests/configagent/configagent.cpp b/messagebus/src/tests/configagent/configagent.cpp
new file mode 100644
index 00000000000..c08fdd25be0
--- /dev/null
+++ b/messagebus/src/tests/configagent/configagent.cpp
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("configagent_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/config/print/fileconfigreader.h>
+#include <vespa/messagebus/configagent.h>
+#include <vespa/messagebus/iconfighandler.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/config-messagebus.h>
+
+using namespace mbus;
+using namespace messagebus;
+using namespace config;
+
+class Test : public vespalib::TestApp, public IConfigHandler {
+private:
+ RoutingSpec _spec;
+ bool checkHalf();
+ bool checkFull();
+ bool checkTables(uint32_t numTables);
+
+public:
+ int Main();
+ bool setupRouting(const RoutingSpec &spec);
+};
+
+TEST_APPHOOK(Test);
+
+bool
+Test::setupRouting(const RoutingSpec &spec)
+{
+ _spec = spec;
+ return true;
+}
+
+bool
+Test::checkTables(uint32_t numTables)
+{
+ if (!EXPECT_EQUAL(numTables, _spec.getNumTables())) return false;
+ if (numTables > 0) {
+ if (!EXPECT_EQUAL("foo", _spec.getTable(0).getProtocol())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(0).getNumHops())) return false;
+ if (!EXPECT_EQUAL("foo-h1", _spec.getTable(0).getHop(0).getName())) return false;
+ if (!EXPECT_EQUAL("foo-h1-sel", _spec.getTable(0).getHop(0).getSelector())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(0).getHop(0).getNumRecipients())) return false;
+ if (!EXPECT_EQUAL("foo-h1-r1", _spec.getTable(0).getHop(0).getRecipient(0))) return false;
+ if (!EXPECT_EQUAL("foo-h1-r2", _spec.getTable(0).getHop(0).getRecipient(1))) return false;
+ if (!EXPECT_EQUAL(true, _spec.getTable(0).getHop(0).getIgnoreResult())) return false;
+ if (!EXPECT_EQUAL("foo-h2", _spec.getTable(0).getHop(1).getName())) return false;
+ if (!EXPECT_EQUAL("foo-h2-sel", _spec.getTable(0).getHop(1).getSelector())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(0).getHop(1).getNumRecipients())) return false;
+ if (!EXPECT_EQUAL("foo-h2-r1", _spec.getTable(0).getHop(1).getRecipient(0))) return false;
+ if (!EXPECT_EQUAL("foo-h2-r2", _spec.getTable(0).getHop(1).getRecipient(1))) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(0).getNumRoutes())) return false;
+ if (!EXPECT_EQUAL("foo-r1", _spec.getTable(0).getRoute(0).getName())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(0).getRoute(0).getNumHops())) return false;
+ if (!EXPECT_EQUAL("foo-h1", _spec.getTable(0).getRoute(0).getHop(0))) return false;
+ if (!EXPECT_EQUAL("foo-h2", _spec.getTable(0).getRoute(0).getHop(1))) return false;
+ if (!EXPECT_EQUAL("foo-r2", _spec.getTable(0).getRoute(1).getName())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(0).getRoute(1).getNumHops())) return false;
+ if (!EXPECT_EQUAL("foo-h2", _spec.getTable(0).getRoute(1).getHop(0))) return false;
+ if (!EXPECT_EQUAL("foo-h1", _spec.getTable(0).getRoute(1).getHop(1))) return false;
+ }
+ if (numTables > 1) {
+ if (!EXPECT_EQUAL("bar", _spec.getTable(1).getProtocol())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(1).getNumHops())) return false;
+ if (!EXPECT_EQUAL("bar-h1", _spec.getTable(1).getHop(0).getName())) return false;
+ if (!EXPECT_EQUAL("bar-h1-sel", _spec.getTable(1).getHop(0).getSelector())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(1).getHop(0).getNumRecipients())) return false;
+ if (!EXPECT_EQUAL("bar-h1-r1", _spec.getTable(1).getHop(0).getRecipient(0))) return false;
+ if (!EXPECT_EQUAL("bar-h1-r2", _spec.getTable(1).getHop(0).getRecipient(1))) return false;
+ if (!EXPECT_EQUAL("bar-h2", _spec.getTable(1).getHop(1).getName())) return false;
+ if (!EXPECT_EQUAL("bar-h2-sel", _spec.getTable(1).getHop(1).getSelector())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(1).getHop(1).getNumRecipients())) return false;
+ if (!EXPECT_EQUAL("bar-h2-r1", _spec.getTable(1).getHop(1).getRecipient(0))) return false;
+ if (!EXPECT_EQUAL("bar-h2-r2", _spec.getTable(1).getHop(1).getRecipient(1))) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(1).getNumRoutes())) return false;
+ if (!EXPECT_EQUAL("bar-r1", _spec.getTable(1).getRoute(0).getName())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(1).getRoute(0).getNumHops())) return false;
+ if (!EXPECT_EQUAL("bar-h1", _spec.getTable(1).getRoute(0).getHop(0))) return false;
+ if (!EXPECT_EQUAL("bar-h2", _spec.getTable(1).getRoute(0).getHop(1))) return false;
+ if (!EXPECT_EQUAL("bar-r2", _spec.getTable(1).getRoute(1).getName())) return false;
+ if (!EXPECT_EQUAL(2u, _spec.getTable(1).getRoute(1).getNumHops())) return false;
+ if (!EXPECT_EQUAL("bar-h2", _spec.getTable(1).getRoute(1).getHop(0))) return false;
+ if (!EXPECT_EQUAL("bar-h1", _spec.getTable(1).getRoute(1).getHop(1))) return false;
+ }
+ return true;
+}
+
+bool
+Test::checkHalf()
+{
+ return _spec.getNumTables() == 1 && EXPECT_TRUE(checkTables(1));
+}
+
+bool
+Test::checkFull()
+{
+ return _spec.getNumTables() == 2 && EXPECT_TRUE(checkTables(2));
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("configagent_test");
+ EXPECT_TRUE(!checkHalf());
+ EXPECT_TRUE(!checkFull());
+ ConfigAgent agent(*this);
+ EXPECT_TRUE(!checkHalf());
+ EXPECT_TRUE(!checkFull());
+ agent.configure(FileConfigReader<MessagebusConfig>("full.cfg").read());
+ EXPECT_TRUE(!checkHalf());
+ EXPECT_TRUE(checkFull());
+ agent.configure(FileConfigReader<MessagebusConfig>("half.cfg").read());
+ EXPECT_TRUE(checkHalf());
+ EXPECT_TRUE(!checkFull());
+ agent.configure(FileConfigReader<MessagebusConfig>("full.cfg").read());
+ EXPECT_TRUE(checkFull());
+ EXPECT_TRUE(!checkHalf());
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/configagent/full.cfg b/messagebus/src/tests/configagent/full.cfg
new file mode 100644
index 00000000000..addfcd3c080
--- /dev/null
+++ b/messagebus/src/tests/configagent/full.cfg
@@ -0,0 +1,44 @@
+routingtable[2]
+routingtable[0].protocol "foo"
+routingtable[0].hop[2]
+routingtable[0].hop[0].name "foo-h1"
+routingtable[0].hop[0].selector "foo-h1-sel"
+routingtable[0].hop[0].recipient[2]
+routingtable[0].hop[0].recipient[0] "foo-h1-r1"
+routingtable[0].hop[0].recipient[1] "foo-h1-r2"
+routingtable[0].hop[0].ignoreresult true
+routingtable[0].hop[1].name "foo-h2"
+routingtable[0].hop[1].selector "foo-h2-sel"
+routingtable[0].hop[1].recipient[2]
+routingtable[0].hop[1].recipient[0] "foo-h2-r1"
+routingtable[0].hop[1].recipient[1] "foo-h2-r2"
+routingtable[0].route[2]
+routingtable[0].route[0].name "foo-r1"
+routingtable[0].route[0].hop[2]
+routingtable[0].route[0].hop[0] "foo-h1"
+routingtable[0].route[0].hop[1] "foo-h2"
+routingtable[0].route[1].name "foo-r2"
+routingtable[0].route[1].hop[2]
+routingtable[0].route[1].hop[0] "foo-h2"
+routingtable[0].route[1].hop[1] "foo-h1"
+routingtable[1].protocol "bar"
+routingtable[1].hop[2]
+routingtable[1].hop[0].name "bar-h1"
+routingtable[1].hop[0].selector "bar-h1-sel"
+routingtable[1].hop[0].recipient[2]
+routingtable[1].hop[0].recipient[0] "bar-h1-r1"
+routingtable[1].hop[0].recipient[1] "bar-h1-r2"
+routingtable[1].hop[1].name "bar-h2"
+routingtable[1].hop[1].selector "bar-h2-sel"
+routingtable[1].hop[1].recipient[2]
+routingtable[1].hop[1].recipient[0] "bar-h2-r1"
+routingtable[1].hop[1].recipient[1] "bar-h2-r2"
+routingtable[1].route[2]
+routingtable[1].route[0].name "bar-r1"
+routingtable[1].route[0].hop[2]
+routingtable[1].route[0].hop[0] "bar-h1"
+routingtable[1].route[0].hop[1] "bar-h2"
+routingtable[1].route[1].name "bar-r2"
+routingtable[1].route[1].hop[2]
+routingtable[1].route[1].hop[0] "bar-h2"
+routingtable[1].route[1].hop[1] "bar-h1"
diff --git a/messagebus/src/tests/configagent/half.cfg b/messagebus/src/tests/configagent/half.cfg
new file mode 100644
index 00000000000..12570a9a557
--- /dev/null
+++ b/messagebus/src/tests/configagent/half.cfg
@@ -0,0 +1,23 @@
+routingtable[1]
+routingtable[0].protocol "foo"
+routingtable[0].hop[2]
+routingtable[0].hop[0].name "foo-h1"
+routingtable[0].hop[0].selector "foo-h1-sel"
+routingtable[0].hop[0].recipient[2]
+routingtable[0].hop[0].recipient[0] "foo-h1-r1"
+routingtable[0].hop[0].recipient[1] "foo-h1-r2"
+routingtable[0].hop[0].ignoreresult true
+routingtable[0].hop[1].name "foo-h2"
+routingtable[0].hop[1].selector "foo-h2-sel"
+routingtable[0].hop[1].recipient[2]
+routingtable[0].hop[1].recipient[0] "foo-h2-r1"
+routingtable[0].hop[1].recipient[1] "foo-h2-r2"
+routingtable[0].route[2]
+routingtable[0].route[0].name "foo-r1"
+routingtable[0].route[0].hop[2]
+routingtable[0].route[0].hop[0] "foo-h1"
+routingtable[0].route[0].hop[1] "foo-h2"
+routingtable[0].route[1].name "foo-r2"
+routingtable[0].route[1].hop[2]
+routingtable[0].route[1].hop[0] "foo-h2"
+routingtable[0].route[1].hop[1] "foo-h1"
diff --git a/messagebus/src/tests/context/.gitignore b/messagebus/src/tests/context/.gitignore
new file mode 100644
index 00000000000..9b9771b64f0
--- /dev/null
+++ b/messagebus/src/tests/context/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+context_test
+messagebus_context_test_app
diff --git a/messagebus/src/tests/context/CMakeLists.txt b/messagebus/src/tests/context/CMakeLists.txt
new file mode 100644
index 00000000000..d9a85a1b8c9
--- /dev/null
+++ b/messagebus/src/tests/context/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_context_test_app
+ SOURCES
+ context.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_context_test_app COMMAND messagebus_context_test_app)
diff --git a/messagebus/src/tests/context/DESC b/messagebus/src/tests/context/DESC
new file mode 100644
index 00000000000..5a40cc4f9a1
--- /dev/null
+++ b/messagebus/src/tests/context/DESC
@@ -0,0 +1 @@
+context test. Take a look at context.cpp for details.
diff --git a/messagebus/src/tests/context/FILES b/messagebus/src/tests/context/FILES
new file mode 100644
index 00000000000..a4c148657b9
--- /dev/null
+++ b/messagebus/src/tests/context/FILES
@@ -0,0 +1 @@
+context.cpp
diff --git a/messagebus/src/tests/context/context.cpp b/messagebus/src/tests/context/context.cpp
new file mode 100644
index 00000000000..ccb136e7b81
--- /dev/null
+++ b/messagebus/src/tests/context/context.cpp
@@ -0,0 +1,102 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("context_test");
+
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+struct Handler : public IMessageHandler
+{
+ DestinationSession::UP session;
+
+ Handler(MessageBus &mb) : session() {
+ session = mb.createDestinationSession("session", true, *this);
+ }
+ ~Handler() {
+ session.reset();
+ }
+ virtual void handleMessage(Message::UP msg) {
+ session->acknowledge(std::move(msg));
+ }
+};
+
+RoutingSpec getRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("test", "test/session"))
+ .addRoute(RouteSpec("test").addHop("test")));
+}
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("context_test");
+
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+ TestServer dst(Identity("test"), getRouting(), slobrok);
+ Handler handler(dst.mb);
+
+ ASSERT_TRUE(src.waitSlobrok("test/session"));
+
+ RoutableQueue queue;
+ SourceSessionParams params;
+ params.setThrottlePolicy(IThrottlePolicy::SP());
+ SourceSession::UP ss = src.mb.createSourceSession(queue, params);
+
+ {
+ Message::UP msg(new SimpleMessage("test", true, 1));
+ msg->setContext(Context((uint64_t)10));
+ ss->send(std::move(msg), "test");
+ }
+ {
+ Message::UP msg(new SimpleMessage("test", true, 1));
+ msg->setContext(Context((uint64_t)20));
+ ss->send(std::move(msg), "test");
+ }
+ {
+ Message::UP msg(new SimpleMessage("test", true, 1));
+ msg->setContext(Context((uint64_t)30));
+ ss->send(std::move(msg), "test");
+ }
+ for (uint32_t i = 0; i < 1000; ++i) {
+ if (queue.size() == 3) {
+ break;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ EXPECT_EQUAL(queue.size(), 3u);
+ {
+ Reply::UP reply = Reply::UP((Reply*)queue.dequeue(0).release());
+ ASSERT_TRUE(reply.get() != 0);
+ EXPECT_EQUAL(reply->getContext().value.UINT64, 10u);
+ }
+ {
+ Reply::UP reply = Reply::UP((Reply*)queue.dequeue(0).release());
+ ASSERT_TRUE(reply.get() != 0);
+ EXPECT_EQUAL(reply->getContext().value.UINT64, 20u);
+ }
+ {
+ Reply::UP reply = Reply::UP((Reply*)queue.dequeue(0).release());
+ ASSERT_TRUE(reply.get() != 0);
+ EXPECT_EQUAL(reply->getContext().value.UINT64, 30u);
+ }
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/create-test.sh b/messagebus/src/tests/create-test.sh
new file mode 100755
index 00000000000..b5406dd24bd
--- /dev/null
+++ b/messagebus/src/tests/create-test.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+gen_ignore_file() {
+ echo "generating '$1' ..."
+ echo ".depend" > $1
+ echo "Makefile" >> $1
+ echo "${test}_test" >> $1
+}
+
+gen_project_file() {
+ echo "generating '$1' ..."
+ echo "APPLICATION ${test}_test" > $1
+ echo "OBJS $test" >> $1
+ echo "LIBS messagebus/testlib/messagebus-test" >> $1
+ echo "LIBS messagebus/messagebus" >> $1
+ echo "EXTERNALLIBS slobrokserver slobrok fnet vespalib config vespalog" >> $1
+ echo "" >> $1
+ echo "CUSTOMMAKE" >> $1
+ echo "test: depend ${test}_test" >> $1
+ echo -e "\t@./${test}_test" >> $1
+}
+
+gen_source() {
+ echo "generating '$1' ..."
+ echo "#include <vespa/log/log.h>" > $1
+ echo "LOG_SETUP(\"${test}_test\");" >> $1
+ echo "#include <vespa/fastos/fastos.h>" >> $1
+ echo "#include <vespa/vespalib/testkit/testapp.h>" >> $1
+ echo "" >> $1
+ echo "// using namespace mbus;" >> $1
+ echo "" >> $1
+ echo "TEST_SETUP(Test);" >> $1
+ echo "" >> $1
+ echo "int" >> $1
+ echo "Test::Main()" >> $1
+ echo "{" >> $1
+ echo " TEST_INIT(\"${test}_test\");" >> $1
+ echo " TEST_DONE();" >> $1
+ echo "}" >> $1
+}
+
+gen_desc() {
+ echo "generating '$1' ..."
+ echo "$test test. Take a look at $test.cpp for details." > $1
+}
+
+gen_file_list() {
+ echo "generating '$1' ..."
+ echo "$test.cpp" > $1
+}
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <name>"
+ echo " name: name of the test to create"
+ exit 1
+fi
+
+test=$1
+if [ -e $test ]; then
+ echo "$test already present, don't want to mess it up..."
+ exit 1
+fi
+
+echo "creating directory '$test' ..."
+mkdir -p $test || exit 1
+cd $test || exit 1
+test=`basename $test`
+
+gen_ignore_file .cvsignore
+gen_project_file fastos.project
+gen_source $test.cpp
+gen_desc DESC
+gen_file_list FILES
diff --git a/messagebus/src/tests/emptyreply/.gitignore b/messagebus/src/tests/emptyreply/.gitignore
new file mode 100644
index 00000000000..bfaf7f812cf
--- /dev/null
+++ b/messagebus/src/tests/emptyreply/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+emptyreply_test
+messagebus_emptyreply_test_app
diff --git a/messagebus/src/tests/emptyreply/CMakeLists.txt b/messagebus/src/tests/emptyreply/CMakeLists.txt
new file mode 100644
index 00000000000..17f26719396
--- /dev/null
+++ b/messagebus/src/tests/emptyreply/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_emptyreply_test_app
+ SOURCES
+ emptyreply.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_emptyreply_test_app COMMAND messagebus_emptyreply_test_app)
diff --git a/messagebus/src/tests/emptyreply/DESC b/messagebus/src/tests/emptyreply/DESC
new file mode 100644
index 00000000000..4db41c3c671
--- /dev/null
+++ b/messagebus/src/tests/emptyreply/DESC
@@ -0,0 +1 @@
+Simple test of the EmptyReply class.
diff --git a/messagebus/src/tests/emptyreply/FILES b/messagebus/src/tests/emptyreply/FILES
new file mode 100644
index 00000000000..5fbc80bb05c
--- /dev/null
+++ b/messagebus/src/tests/emptyreply/FILES
@@ -0,0 +1 @@
+emptyreply.cpp
diff --git a/messagebus/src/tests/emptyreply/emptyreply.cpp b/messagebus/src/tests/emptyreply/emptyreply.cpp
new file mode 100644
index 00000000000..711a21edd17
--- /dev/null
+++ b/messagebus/src/tests/emptyreply/emptyreply.cpp
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("emptyreply_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/emptyreply.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("emptyreply_test");
+ Reply::UP empty(new EmptyReply());
+ EXPECT_TRUE(empty->isReply());
+ EXPECT_TRUE(empty->getProtocol() == "");
+ EXPECT_TRUE(empty->getType() == 0);
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/error/.gitignore b/messagebus/src/tests/error/.gitignore
new file mode 100644
index 00000000000..3023dc1cb7a
--- /dev/null
+++ b/messagebus/src/tests/error/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+error_test
+messagebus_error_test_app
diff --git a/messagebus/src/tests/error/CMakeLists.txt b/messagebus/src/tests/error/CMakeLists.txt
new file mode 100644
index 00000000000..7a5ea78fab3
--- /dev/null
+++ b/messagebus/src/tests/error/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_error_test_app
+ SOURCES
+ error.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_error_test_app COMMAND messagebus_error_test_app)
diff --git a/messagebus/src/tests/error/DESC b/messagebus/src/tests/error/DESC
new file mode 100644
index 00000000000..87bd0cc23fa
--- /dev/null
+++ b/messagebus/src/tests/error/DESC
@@ -0,0 +1 @@
+error test. Take a look at error.cpp for details.
diff --git a/messagebus/src/tests/error/FILES b/messagebus/src/tests/error/FILES
new file mode 100644
index 00000000000..779aee64a2c
--- /dev/null
+++ b/messagebus/src/tests/error/FILES
@@ -0,0 +1 @@
+error.cpp
diff --git a/messagebus/src/tests/error/error.cpp b/messagebus/src/tests/error/error.cpp
new file mode 100644
index 00000000000..d5779aadeeb
--- /dev/null
+++ b/messagebus/src/tests/error/error.cpp
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("error_test");
+
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+RoutingSpec getRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("pxy", "test/pxy/session"))
+ .addHop(HopSpec("dst", "test/dst/session"))
+ .addRoute(RouteSpec("test").addHop("pxy").addHop("dst")));
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("error_test");
+
+ Slobrok slobrok;
+ TestServer srcNet(Identity("test/src"), getRouting(), slobrok);
+ TestServer pxyNet(Identity("test/pxy"), getRouting(), slobrok);
+ TestServer dstNet(Identity("test/dst"), getRouting(), slobrok);
+
+ Receptor src;
+ Receptor pxy;
+ Receptor dst;
+
+ SourceSession::UP ss = srcNet.mb.createSourceSession(src, SourceSessionParams());
+ IntermediateSession::UP is = pxyNet.mb.createIntermediateSession("session", true, pxy, pxy);
+ DestinationSession::UP ds = dstNet.mb.createDestinationSession("session", true, dst);
+
+ ASSERT_TRUE(srcNet.waitSlobrok("test/pxy/session"));
+ ASSERT_TRUE(srcNet.waitSlobrok("test/dst/session"));
+ ASSERT_TRUE(pxyNet.waitSlobrok("test/dst/session"));
+
+ for (int i = 0; i < 5; i++) {
+ ASSERT_TRUE(ss->send(SimpleMessage::UP(new SimpleMessage("test message")), "test").isAccepted());
+ Message::UP msg = pxy.getMessage();
+ ASSERT_TRUE(msg.get() != 0);
+ is->forward(std::move(msg));
+
+ msg = dst.getMessage();
+ ASSERT_TRUE(msg.get() != 0);
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR, "fatality"));
+ ds->reply(std::move(reply));
+
+ reply = pxy.getReply();
+ ASSERT_TRUE(reply.get() != 0);
+ EXPECT_EQUAL(reply->getNumErrors(), 1u);
+ EXPECT_EQUAL(reply->getError(0).getService(), "test/dst/session");
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR, "fatality"));
+ is->forward(std::move(reply));
+
+ reply = src.getReply();
+ ASSERT_TRUE(reply.get() != 0);
+ EXPECT_EQUAL(reply->getNumErrors(), 2u);
+ EXPECT_EQUAL(reply->getError(0).getService(), "test/dst/session");
+ EXPECT_EQUAL(reply->getError(1).getService(), "test/pxy/session");
+ }
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/identity/.gitignore b/messagebus/src/tests/identity/.gitignore
new file mode 100644
index 00000000000..9dd069cdb7a
--- /dev/null
+++ b/messagebus/src/tests/identity/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+identity_test
+messagebus_identity_test_app
diff --git a/messagebus/src/tests/identity/CMakeLists.txt b/messagebus/src/tests/identity/CMakeLists.txt
new file mode 100644
index 00000000000..66aea485746
--- /dev/null
+++ b/messagebus/src/tests/identity/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_identity_test_app
+ SOURCES
+ identity.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_identity_test_app COMMAND messagebus_identity_test_app)
diff --git a/messagebus/src/tests/identity/DESC b/messagebus/src/tests/identity/DESC
new file mode 100644
index 00000000000..a1fdfb95f8b
--- /dev/null
+++ b/messagebus/src/tests/identity/DESC
@@ -0,0 +1 @@
+Test that the network identity may be obtained from config.
diff --git a/messagebus/src/tests/identity/FILES b/messagebus/src/tests/identity/FILES
new file mode 100644
index 00000000000..484caddcac7
--- /dev/null
+++ b/messagebus/src/tests/identity/FILES
@@ -0,0 +1,2 @@
+identity.cpp
+test.cfg
diff --git a/messagebus/src/tests/identity/identity.cpp b/messagebus/src/tests/identity/identity.cpp
new file mode 100644
index 00000000000..95c499e3ff2
--- /dev/null
+++ b/messagebus/src/tests/identity/identity.cpp
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("identity_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/network/identity.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("identity_test");
+ Identity ident("foo/bar/baz");
+ EXPECT_TRUE(ident.getServicePrefix() == "foo/bar/baz");
+ {
+ std::vector<string> tmp = Identity::split("foo/bar/baz");
+ ASSERT_TRUE(tmp.size() == 3);
+ EXPECT_TRUE(tmp[0] == "foo");
+ EXPECT_TRUE(tmp[1] == "bar");
+ EXPECT_TRUE(tmp[2] == "baz");
+ }
+ {
+ std::vector<string> tmp = Identity::split("//");
+ ASSERT_TRUE(tmp.size() == 3);
+ EXPECT_TRUE(tmp[0] == "");
+ EXPECT_TRUE(tmp[1] == "");
+ EXPECT_TRUE(tmp[2] == "");
+ }
+ {
+ std::vector<string> tmp = Identity::split("foo");
+ ASSERT_TRUE(tmp.size() == 1);
+ EXPECT_TRUE(tmp[0] == "foo");
+ }
+ {
+ std::vector<string> tmp = Identity::split("");
+ ASSERT_TRUE(tmp.size() == 1);
+ EXPECT_TRUE(tmp[0] == "");
+ }
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/loadbalance/.gitignore b/messagebus/src/tests/loadbalance/.gitignore
new file mode 100644
index 00000000000..d1cbb5977f1
--- /dev/null
+++ b/messagebus/src/tests/loadbalance/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+loadbalance_test
+messagebus_loadbalance_test_app
diff --git a/messagebus/src/tests/loadbalance/CMakeLists.txt b/messagebus/src/tests/loadbalance/CMakeLists.txt
new file mode 100644
index 00000000000..68d0483ce5d
--- /dev/null
+++ b/messagebus/src/tests/loadbalance/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_loadbalance_test_app
+ SOURCES
+ loadbalance.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_loadbalance_test_app COMMAND messagebus_loadbalance_test_app)
diff --git a/messagebus/src/tests/loadbalance/DESC b/messagebus/src/tests/loadbalance/DESC
new file mode 100644
index 00000000000..67009371472
--- /dev/null
+++ b/messagebus/src/tests/loadbalance/DESC
@@ -0,0 +1,2 @@
+Test that service patterns with '*' performs load balancing between
+the services the pattern resolves to.
diff --git a/messagebus/src/tests/loadbalance/FILES b/messagebus/src/tests/loadbalance/FILES
new file mode 100644
index 00000000000..6b28cce1716
--- /dev/null
+++ b/messagebus/src/tests/loadbalance/FILES
@@ -0,0 +1 @@
+loadbalance.cpp
diff --git a/messagebus/src/tests/loadbalance/loadbalance.cpp b/messagebus/src/tests/loadbalance/loadbalance.cpp
new file mode 100644
index 00000000000..f49ca13708c
--- /dev/null
+++ b/messagebus/src/tests/loadbalance/loadbalance.cpp
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("loadbalance_test");
+
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+
+using namespace mbus;
+
+struct Handler : public IMessageHandler
+{
+ DestinationSession::UP session;
+ uint32_t cnt;
+
+ Handler(MessageBus &mb) : session(), cnt(0) {
+ session = mb.createDestinationSession("session", true, *this);
+ }
+ ~Handler() {
+ session.reset();
+ }
+ virtual void handleMessage(Message::UP msg) {
+ ++cnt;
+ session->acknowledge(std::move(msg));
+ }
+};
+
+RoutingSpec getRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("dst", "test/*/session"))
+ .addRoute(RouteSpec("test").addHop("dst")));
+}
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("loadbalance_test");
+
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+ TestServer dst1(Identity("test/dst1"), getRouting(), slobrok);
+ TestServer dst2(Identity("test/dst2"), getRouting(), slobrok);
+ TestServer dst3(Identity("test/dst3"), getRouting(), slobrok);
+
+ Handler h1(dst1.mb);
+ Handler h2(dst2.mb);
+ Handler h3(dst3.mb);
+
+ ASSERT_TRUE(src.waitSlobrok("test/dst1/session"));
+ ASSERT_TRUE(src.waitSlobrok("test/dst2/session"));
+ ASSERT_TRUE(src.waitSlobrok("test/dst3/session"));
+
+ RoutableQueue queue;
+ SourceSessionParams params;
+ params.setTimeout(30.0);
+ params.setThrottlePolicy(IThrottlePolicy::SP());
+ SourceSession::UP ss = src.mb.createSourceSession(queue, params);
+
+ uint32_t msgCnt = 90;
+ ASSERT_TRUE(msgCnt % 3 == 0);
+ for (uint32_t i = 0; i < msgCnt; ++i) {
+ ss->send(Message::UP(new SimpleMessage("test")), "test");
+ }
+ for (uint32_t i = 0; i < 1000; ++i) {
+ if (queue.size() == msgCnt) {
+ break;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ EXPECT_TRUE(queue.size() == msgCnt);
+ EXPECT_TRUE(h1.cnt == msgCnt / 3);
+ EXPECT_TRUE(h2.cnt == msgCnt / 3);
+ EXPECT_TRUE(h3.cnt == msgCnt / 3);
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/messagebus/.gitignore b/messagebus/src/tests/messagebus/.gitignore
new file mode 100644
index 00000000000..b8b2aa5313c
--- /dev/null
+++ b/messagebus/src/tests/messagebus/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+messagebus_test
+messagebus_messagebus_test_app
diff --git a/messagebus/src/tests/messagebus/CMakeLists.txt b/messagebus/src/tests/messagebus/CMakeLists.txt
new file mode 100644
index 00000000000..fc44bb60069
--- /dev/null
+++ b/messagebus/src/tests/messagebus/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_messagebus_test_app
+ SOURCES
+ messagebus.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_messagebus_test_app COMMAND messagebus_messagebus_test_app)
diff --git a/messagebus/src/tests/messagebus/DESC b/messagebus/src/tests/messagebus/DESC
new file mode 100644
index 00000000000..19eb03c7048
--- /dev/null
+++ b/messagebus/src/tests/messagebus/DESC
@@ -0,0 +1 @@
+Generic messagebus test ported from Java.
diff --git a/messagebus/src/tests/messagebus/FILES b/messagebus/src/tests/messagebus/FILES
new file mode 100644
index 00000000000..0430f52149a
--- /dev/null
+++ b/messagebus/src/tests/messagebus/FILES
@@ -0,0 +1 @@
+messagebus.cpp
diff --git a/messagebus/src/tests/messagebus/messagebus.cpp b/messagebus/src/tests/messagebus/messagebus.cpp
new file mode 100644
index 00000000000..a887759ac02
--- /dev/null
+++ b/messagebus/src/tests/messagebus/messagebus.cpp
@@ -0,0 +1,538 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("messagebus_test");
+
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/routing/route.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/messagebus/routing/routingnodeiterator.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+
+using namespace mbus;
+
+struct Base {
+ RoutableQueue queue;
+ Base() : queue() {}
+ virtual ~Base() {
+ while (queue.size() > 0) {
+ Routable::UP r = queue.dequeue(0);
+ r->getCallStack().discard();
+ }
+ }
+ RoutingSpec getRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("DocProc", "docproc/*/session"))
+ .addHop(HopSpec("Search", "search/[All]/[Hash]/session")
+ .addRecipient("search/r.0/c.0/session")
+ .addRecipient("search/r.0/c.1/session")
+ .addRecipient("search/r.1/c.0/session")
+ .addRecipient("search/r.1/c.1/session"))
+ .addRoute(RouteSpec("Index").addHop("DocProc").addHop("Search"))
+ .addRoute(RouteSpec("DocProc").addHop("DocProc"))
+ .addRoute(RouteSpec("Search").addHop("Search")));
+ }
+ bool waitQueueSize(uint32_t size) {
+ for (uint32_t i = 0; i < 1000; ++i) {
+ if (queue.size() == size) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ return false;
+ }
+};
+
+struct Client : public Base {
+ typedef std::unique_ptr<Client> UP;
+ TestServer server;
+ SourceSession::UP session;
+ Client(Slobrok &slobrok)
+ : Base(), server(Identity(""), getRouting(), slobrok), session()
+ {
+ SourceSessionParams params;
+ params.setThrottlePolicy(IThrottlePolicy::SP());
+ session = server.mb.createSourceSession(queue, params);
+
+ }
+};
+
+struct Server : public Base {
+ TestServer server;
+ Server(const string &name, Slobrok &slobrok)
+ : Base(), server(Identity(name), getRouting(), slobrok)
+ {
+ // empty
+ }
+};
+
+struct DocProc : public Server {
+ typedef std::unique_ptr<DocProc> UP;
+ IntermediateSession::UP session;
+ DocProc(const string &name, Slobrok &slobrok)
+ : Server(name, slobrok), session()
+ {
+ session = server.mb.createIntermediateSession("session", true, queue, queue);
+ }
+};
+
+struct Search : public Server {
+ typedef std::unique_ptr<Search> UP;
+ DestinationSession::UP session;
+ Search(const string &name, Slobrok &slobrok)
+ : Server(name, slobrok), session()
+ {
+ session = server.mb.createDestinationSession("session", true, queue);
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+class Test : public vespalib::TestApp {
+private:
+ Slobrok::UP slobrok;
+ Client::UP client;
+ DocProc::UP dp0;
+ DocProc::UP dp1;
+ DocProc::UP dp2;
+ Search::UP search00;
+ Search::UP search01;
+ Search::UP search10;
+ Search::UP search11;
+ std::vector<DocProc*> dpVec;
+ std::vector<Search*> searchVec;
+
+public:
+ int Main();
+ void testSendToAny();
+ void testSendToCol();
+ void testSendToAnyThenCol();
+ void testDirectHop();
+ void testDirectRoute();
+ void testRoutingPolicyCache();
+ void debugTrace();
+
+private:
+ void setup();
+ void teardown();
+
+ void assertSrc(Client& src);
+ void assertItr(DocProc& itr);
+ void assertDst(Search& dst);
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("messagebus_test");
+
+ testSendToAny(); TEST_FLUSH();
+ testSendToCol(); TEST_FLUSH();
+ testSendToAnyThenCol(); TEST_FLUSH();
+ testDirectHop(); TEST_FLUSH();
+ testDirectRoute(); TEST_FLUSH();
+ testRoutingPolicyCache(); TEST_FLUSH();
+ debugTrace(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+void
+Test::setup()
+{
+ slobrok.reset(new Slobrok());
+ client.reset(new Client(*slobrok));
+ dp0.reset(new DocProc("docproc/0", *slobrok));
+ dp1.reset(new DocProc("docproc/1", *slobrok));
+ dp2.reset(new DocProc("docproc/2", *slobrok));
+ search00.reset(new Search("search/r.0/c.0", *slobrok));
+ search01.reset(new Search("search/r.0/c.1", *slobrok));
+ search10.reset(new Search("search/r.1/c.0", *slobrok));
+ search11.reset(new Search("search/r.1/c.1", *slobrok));
+ dpVec.push_back(dp0.get());
+ dpVec.push_back(dp1.get());
+ dpVec.push_back(dp2.get());
+ searchVec.push_back(search00.get());
+ searchVec.push_back(search01.get());
+ searchVec.push_back(search10.get());
+ searchVec.push_back(search11.get());
+ ASSERT_TRUE(client->server.waitSlobrok("docproc/0/session"));
+ ASSERT_TRUE(client->server.waitSlobrok("docproc/1/session"));
+ ASSERT_TRUE(client->server.waitSlobrok("docproc/2/session"));
+ ASSERT_TRUE(client->server.waitSlobrok("search/r.0/c.0/session"));
+ ASSERT_TRUE(client->server.waitSlobrok("search/r.0/c.1/session"));
+ ASSERT_TRUE(client->server.waitSlobrok("search/r.1/c.0/session"));
+ ASSERT_TRUE(client->server.waitSlobrok("search/r.1/c.1/session"));
+ ASSERT_TRUE(dp0->server.waitSlobrok("search/r.0/c.0/session"));
+ ASSERT_TRUE(dp0->server.waitSlobrok("search/r.0/c.1/session"));
+ ASSERT_TRUE(dp0->server.waitSlobrok("search/r.1/c.0/session"));
+ ASSERT_TRUE(dp0->server.waitSlobrok("search/r.1/c.1/session"));
+ ASSERT_TRUE(dp1->server.waitSlobrok("search/r.0/c.0/session"));
+ ASSERT_TRUE(dp1->server.waitSlobrok("search/r.0/c.1/session"));
+ ASSERT_TRUE(dp1->server.waitSlobrok("search/r.1/c.0/session"));
+ ASSERT_TRUE(dp1->server.waitSlobrok("search/r.1/c.1/session"));
+ ASSERT_TRUE(dp2->server.waitSlobrok("search/r.0/c.0/session"));
+ ASSERT_TRUE(dp2->server.waitSlobrok("search/r.0/c.1/session"));
+ ASSERT_TRUE(dp2->server.waitSlobrok("search/r.1/c.0/session"));
+ ASSERT_TRUE(dp2->server.waitSlobrok("search/r.1/c.1/session"));
+}
+
+void Test::teardown()
+{
+ dpVec.clear();
+ searchVec.clear();
+ search11.reset();
+ search10.reset();
+ search01.reset();
+ search00.reset();
+ dp2.reset();
+ dp1.reset();
+ dp0.reset();
+ client.reset();
+ slobrok.reset();
+}
+
+void
+Test::testSendToAny()
+{
+ setup();
+ for (uint32_t i = 0; i < 300; ++i) {
+ Message::UP msg(new SimpleMessage("test"));
+ EXPECT_TRUE(client->session->send(std::move(msg), "DocProc").isAccepted());
+ }
+ EXPECT_TRUE(dp0->waitQueueSize(100));
+ EXPECT_TRUE(dp1->waitQueueSize(100));
+ EXPECT_TRUE(dp2->waitQueueSize(100));
+ for (uint32_t i = 0; i < dpVec.size(); ++i) {
+ DocProc *p = dpVec[i];
+ while (p->queue.size() > 0) {
+ Routable::UP msg = p->queue.dequeue(0);
+ ASSERT_TRUE(msg.get() != 0);
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ reply->addError(Error(ErrorCode::FATAL_ERROR, ""));
+ p->session->forward(std::move(reply));
+ }
+ }
+ EXPECT_TRUE(client->waitQueueSize(300));
+ while (client->queue.size() > 0) {
+ Routable::UP reply = client->queue.dequeue(0);
+ ASSERT_TRUE(reply.get() != 0);
+ ASSERT_TRUE(reply->isReply());
+ EXPECT_TRUE(static_cast<Reply&>(*reply).getNumErrors() == 1);
+ }
+ teardown();
+}
+
+void
+Test::testSendToCol()
+{
+ setup();
+ ASSERT_TRUE(SimpleMessage("msg").getHash() % 2 == 0);
+ for (uint32_t i = 0; i < 150; ++i) {
+ Message::UP msg(new SimpleMessage("msg"));
+ EXPECT_TRUE(client->session->send(std::move(msg), "Search").isAccepted());
+ }
+ EXPECT_TRUE(search00->waitQueueSize(150));
+ EXPECT_TRUE(search01->waitQueueSize(0));
+ EXPECT_TRUE(search10->waitQueueSize(150));
+ EXPECT_TRUE(search11->waitQueueSize(0));
+ ASSERT_TRUE(SimpleMessage("msh").getHash() % 2 == 1);
+ for (uint32_t i = 0; i < 150; ++i) {
+ Message::UP msg(new SimpleMessage("msh"));
+ ASSERT_TRUE(client->session->send(std::move(msg), "Search").isAccepted());
+ }
+ EXPECT_TRUE(search00->waitQueueSize(150));
+ EXPECT_TRUE(search01->waitQueueSize(150));
+ EXPECT_TRUE(search10->waitQueueSize(150));
+ EXPECT_TRUE(search11->waitQueueSize(150));
+ for (uint32_t i = 0; i < searchVec.size(); ++i) {
+ Search *s = searchVec[i];
+ while (s->queue.size() > 0) {
+ Routable::UP msg = s->queue.dequeue(0);
+ ASSERT_TRUE(msg.get() != 0);
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ s->session->reply(std::move(reply));
+ }
+ }
+ client->waitQueueSize(300);
+ FastOS_Thread::Sleep(100);
+ client->waitQueueSize(300);
+ while (client->queue.size() > 0) {
+ Routable::UP reply = client->queue.dequeue(0);
+ ASSERT_TRUE(reply.get() != 0);
+ ASSERT_TRUE(reply->isReply());
+ EXPECT_TRUE(static_cast<Reply&>(*reply).getNumErrors() == 0);
+ }
+ teardown();
+}
+
+void
+Test::testSendToAnyThenCol()
+{
+ setup();
+ ASSERT_TRUE(SimpleMessage("msg").getHash() % 2 == 0);
+ for (uint32_t i = 0; i < 150; ++i) {
+ Message::UP msg(new SimpleMessage("msg"));
+ EXPECT_TRUE(client->session->send(std::move(msg), "Index").isAccepted());
+ }
+ EXPECT_TRUE(dp0->waitQueueSize(50));
+ EXPECT_TRUE(dp1->waitQueueSize(50));
+ EXPECT_TRUE(dp2->waitQueueSize(50));
+ for (uint32_t i = 0; i < dpVec.size(); ++i) {
+ DocProc *p = dpVec[i];
+ while (p->queue.size() > 0) {
+ Routable::UP r = p->queue.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ p->session->forward(std::move(r));
+ }
+ }
+ EXPECT_TRUE(search00->waitQueueSize(150));
+ EXPECT_TRUE(search01->waitQueueSize(0));
+ EXPECT_TRUE(search10->waitQueueSize(150));
+ EXPECT_TRUE(search11->waitQueueSize(0));
+ ASSERT_TRUE(SimpleMessage("msh").getHash() % 2 == 1);
+ for (uint32_t i = 0; i < 150; ++i) {
+ Message::UP msg(new SimpleMessage("msh"));
+ ASSERT_TRUE(client->session->send(std::move(msg), "Index").isAccepted());
+ }
+ EXPECT_TRUE(dp0->waitQueueSize(50));
+ EXPECT_TRUE(dp1->waitQueueSize(50));
+ EXPECT_TRUE(dp2->waitQueueSize(50));
+ for (uint32_t i = 0; i < dpVec.size(); ++i) {
+ DocProc *p = dpVec[i];
+ while (p->queue.size() > 0) {
+ Routable::UP r = p->queue.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ p->session->forward(std::move(r));
+ }
+ }
+ EXPECT_TRUE(search00->waitQueueSize(150));
+ EXPECT_TRUE(search01->waitQueueSize(150));
+ EXPECT_TRUE(search10->waitQueueSize(150));
+ EXPECT_TRUE(search11->waitQueueSize(150));
+ for (uint32_t i = 0; i < searchVec.size(); ++i) {
+ Search *s = searchVec[i];
+ while (s->queue.size() > 0) {
+ Routable::UP msg = s->queue.dequeue(0);
+ ASSERT_TRUE(msg.get() != 0);
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ s->session->reply(std::move(reply));
+ }
+ }
+ EXPECT_TRUE(dp0->waitQueueSize(100));
+ EXPECT_TRUE(dp1->waitQueueSize(100));
+ EXPECT_TRUE(dp2->waitQueueSize(100));
+ for (uint32_t i = 0; i < dpVec.size(); ++i) {
+ DocProc *p = dpVec[i];
+ while (p->queue.size() > 0) {
+ Routable::UP r = p->queue.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ p->session->forward(std::move(r));
+ }
+ }
+ client->waitQueueSize(300);
+ FastOS_Thread::Sleep(100);
+ client->waitQueueSize(300);
+ while (client->queue.size() > 0) {
+ Routable::UP reply = client->queue.dequeue(0);
+ ASSERT_TRUE(reply.get() != 0);
+ ASSERT_TRUE(reply->isReply());
+ EXPECT_TRUE(static_cast<Reply&>(*reply).getNumErrors() == 0);
+ }
+ teardown();
+}
+
+void
+Test::testDirectHop()
+{
+ setup();
+ for (int row = 0; row < 2; row++) {
+ for (int col = 0; col < 2; col++) {
+ Search* dst = searchVec[row * 2 + col];
+
+ // Send using name.
+ ASSERT_TRUE(client->session->send(
+ Message::UP(new SimpleMessage("empty")),
+ Route().addHop(vespalib::make_vespa_string("search/r.%d/c.%d/session", row, col)))
+ .isAccepted());
+ assertDst(*dst);
+ assertSrc(*client);
+
+ // Send using address.
+ ASSERT_TRUE(client->session->send(
+ Message::UP(new SimpleMessage("empty")),
+ Route().addHop(Hop(dst->session->getConnectionSpec().c_str())))
+ .isAccepted());
+ assertDst(*dst);
+ assertSrc(*client);
+ }
+ }
+ teardown();
+}
+
+void
+Test::testDirectRoute()
+{
+ setup();
+ ASSERT_TRUE(client->session->send(
+ Message::UP(new SimpleMessage("empty")),
+ Route()
+ .addHop(Hop("docproc/0/session"))
+ .addHop(Hop(dp0->session->getConnectionSpec()))
+ .addHop(Hop("docproc/1/session"))
+ .addHop(Hop(dp1->session->getConnectionSpec()))
+ .addHop(Hop("docproc/2/session"))
+ .addHop(Hop(dp2->session->getConnectionSpec()))
+ .addHop(Hop("search/r.0/c.0/session")))
+ .isAccepted());
+ assertItr(*dp0);
+ assertItr(*dp0);
+ assertItr(*dp1);
+ assertItr(*dp1);
+ assertItr(*dp2);
+ assertItr(*dp2);
+ assertDst(*search00);
+ assertItr(*dp2);
+ assertItr(*dp2);
+ assertItr(*dp1);
+ assertItr(*dp1);
+ assertItr(*dp0);
+ assertItr(*dp0);
+ assertSrc(*client);
+
+ teardown();
+}
+
+void
+Test::assertDst(Search& dst)
+{
+ ASSERT_TRUE(dst.waitQueueSize(1));
+ Routable::UP msg = dst.queue.dequeue(0);
+ ASSERT_TRUE(msg.get() != 0);
+ dst.session->acknowledge(Message::UP(static_cast<Message*>(msg.release())));
+}
+
+void
+Test::assertItr(DocProc& itr)
+{
+ ASSERT_TRUE(itr.waitQueueSize(1));
+ Routable::UP msg = itr.queue.dequeue(0);
+ ASSERT_TRUE(msg.get() != 0);
+ itr.session->forward(std::move(msg));
+}
+
+void
+Test::assertSrc(Client& src)
+{
+ ASSERT_TRUE(src.waitQueueSize(1));
+ Routable::UP msg = src.queue.dequeue(0);
+ ASSERT_TRUE(msg.get() != 0);
+}
+
+void
+Test::testRoutingPolicyCache()
+{
+ setup();
+ MessageBus &bus = client->server.mb;
+
+ IRoutingPolicy::SP all = bus.getRoutingPolicy(SimpleProtocol::NAME, "All", "");
+ ASSERT_TRUE(all.get() != NULL);
+
+ IRoutingPolicy::SP ref = bus.getRoutingPolicy(SimpleProtocol::NAME, "All", "");
+ ASSERT_TRUE(ref.get() != NULL);
+ ASSERT_TRUE(all.get() == ref.get());
+
+ IRoutingPolicy::SP allArg = bus.getRoutingPolicy(SimpleProtocol::NAME, "All", "Arg");
+ ASSERT_TRUE(allArg.get() != NULL);
+ ASSERT_TRUE(all.get() != allArg.get());
+
+ IRoutingPolicy::SP refArg = bus.getRoutingPolicy(SimpleProtocol::NAME, "All", "Arg");
+ ASSERT_TRUE(refArg.get() != NULL);
+ ASSERT_TRUE(allArg.get() == refArg.get());
+
+ teardown();
+}
+
+void
+Test::debugTrace()
+{
+ setup();
+ ASSERT_TRUE(SimpleMessage("msg").getHash() % 2 == 0);
+ for (uint32_t i = 0; i < 3; ++i) {
+ Message::UP msg(new SimpleMessage("msg"));
+ msg->getTrace().setLevel(4 + i);
+ EXPECT_TRUE(client->session->send(std::move(msg), "Index").isAccepted());
+ }
+ EXPECT_TRUE(dp0->waitQueueSize(1));
+ EXPECT_TRUE(dp1->waitQueueSize(1));
+ EXPECT_TRUE(dp2->waitQueueSize(1));
+ for (uint32_t i = 0; i < dpVec.size(); ++i) {
+ DocProc *p = dpVec[i];
+ while (p->queue.size() > 0) {
+ Routable::UP r = p->queue.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ p->session->forward(std::move(r));
+ }
+ }
+ EXPECT_TRUE(search00->waitQueueSize(3));
+ EXPECT_TRUE(search01->waitQueueSize(0));
+ EXPECT_TRUE(search10->waitQueueSize(3));
+ EXPECT_TRUE(search11->waitQueueSize(0));
+ for (uint32_t i = 0; i < searchVec.size(); ++i) {
+ Search *s = searchVec[i];
+ while (s->queue.size() > 0) {
+ Routable::UP msg = s->queue.dequeue(0);
+ ASSERT_TRUE(msg.get() != 0);
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ s->session->reply(std::move(reply));
+ }
+ }
+ EXPECT_TRUE(dp0->waitQueueSize(1));
+ EXPECT_TRUE(dp1->waitQueueSize(1));
+ EXPECT_TRUE(dp2->waitQueueSize(1));
+ for (uint32_t i = 0; i < dpVec.size(); ++i) {
+ DocProc *p = dpVec[i];
+ while (p->queue.size() > 0) {
+ Routable::UP r = p->queue.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ p->session->forward(std::move(r));
+ }
+ }
+ client->waitQueueSize(3);
+ Routable::UP reply = client->queue.dequeue(0);
+ fprintf(stderr, "\nTRACE DUMP(level=%d):\n%s\n\n",
+ reply->getTrace().getLevel(),
+ reply->getTrace().toString().c_str());
+ reply = client->queue.dequeue(0);
+ fprintf(stderr, "\nTRACE DUMP(level=%d):\n%s\n\n",
+ reply->getTrace().getLevel(),
+ reply->getTrace().toString().c_str());
+ reply = client->queue.dequeue(0);
+ fprintf(stderr, "\nTRACE DUMP(level=%d):\n%s\n\n",
+ reply->getTrace().getLevel(),
+ reply->getTrace().toString().c_str());
+ teardown();
+}
diff --git a/messagebus/src/tests/messageordering/.gitignore b/messagebus/src/tests/messageordering/.gitignore
new file mode 100644
index 00000000000..1e4e97670de
--- /dev/null
+++ b/messagebus/src/tests/messageordering/.gitignore
@@ -0,0 +1,3 @@
+/.depend
+/Makefile
+messagebus_messageordering_test_app
diff --git a/messagebus/src/tests/messageordering/CMakeLists.txt b/messagebus/src/tests/messageordering/CMakeLists.txt
new file mode 100644
index 00000000000..b3af6386684
--- /dev/null
+++ b/messagebus/src/tests/messageordering/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_messageordering_test_app
+ SOURCES
+ messageordering.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(
+ NAME messagebus_messageordering_test_app
+ COMMAND messagebus_messageordering_test_app
+)
diff --git a/messagebus/src/tests/messageordering/DESC b/messagebus/src/tests/messageordering/DESC
new file mode 100644
index 00000000000..a4e636441ac
--- /dev/null
+++ b/messagebus/src/tests/messageordering/DESC
@@ -0,0 +1 @@
+messageordering test. Take a look at messageordering.cpp for details.
diff --git a/messagebus/src/tests/messageordering/FILES b/messagebus/src/tests/messageordering/FILES
new file mode 100644
index 00000000000..51c47a40211
--- /dev/null
+++ b/messagebus/src/tests/messageordering/FILES
@@ -0,0 +1 @@
+messageordering.cpp
diff --git a/messagebus/src/tests/messageordering/messageordering.cpp b/messagebus/src/tests/messageordering/messageordering.cpp
new file mode 100644
index 00000000000..97daee80d99
--- /dev/null
+++ b/messagebus/src/tests/messageordering/messageordering.cpp
@@ -0,0 +1,178 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("messageordering_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <stdexcept>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+RoutingSpec
+getRouting()
+{
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("dst", "test/dst/session"))
+ .addRoute(RouteSpec("test").addHop("dst")));
+}
+
+class MultiReceptor : public IMessageHandler
+{
+private:
+ vespalib::Monitor _mon;
+ DestinationSession* _destinationSession;
+ int _messageCounter;
+
+ MultiReceptor(const Receptor &);
+ MultiReceptor &operator=(const Receptor &);
+public:
+ MultiReceptor()
+ : _mon(),
+ _destinationSession(0),
+ _messageCounter(0)
+ {}
+ virtual void handleMessage(Message::UP msg)
+ {
+ SimpleMessage& simpleMsg(dynamic_cast<SimpleMessage&>(*msg));
+ LOG(spam, "Attempting to acquire lock for %s",
+ simpleMsg.getValue().c_str());
+
+ vespalib::MonitorGuard lock(_mon);
+
+ vespalib::string expected(vespalib::make_vespa_string("%d", _messageCounter));
+ LOG(debug, "Got message %p with %s, expecting %s",
+ msg.get(),
+ simpleMsg.getValue().c_str(),
+ expected.c_str());
+
+ SimpleReply::UP sr(new SimpleReply("test reply"));
+ msg->swapState(*sr);
+
+ if (simpleMsg.getValue() != expected) {
+ std::stringstream ss;
+ ss << "Received out-of-sequence message! Expected "
+ << expected
+ << ", but got "
+ << simpleMsg.getValue();
+ //LOG(warning, "%s", ss.str().c_str());
+ sr->addError(Error(ErrorCode::FATAL_ERROR, ss.str()));
+ }
+ sr->setValue(simpleMsg.getValue());
+
+ ++_messageCounter;
+ _destinationSession->reply(Reply::UP(sr.release()));
+ }
+ void setDestinationSession(DestinationSession& sess) {
+ _destinationSession = &sess;
+ }
+};
+
+class VerifyReplyReceptor : public IReplyHandler
+{
+ vespalib::Monitor _mon;
+ std::string _failure;
+ int _replyCount;
+public:
+ VerifyReplyReceptor()
+ : _mon(),
+ _failure(),
+ _replyCount(0)
+ {}
+ void handleReply(Reply::UP reply)
+ {
+ vespalib::MonitorGuard lock(_mon);
+ if (reply->hasErrors()) {
+ std::ostringstream ss;
+ ss << "Reply failed with "
+ << reply->getError(0).getMessage()
+ << "\n"
+ << reply->getTrace().toString();
+ if (_failure.empty()) {
+ _failure = ss.str();
+ }
+ LOG(warning, "%s", ss.str().c_str());
+ } else {
+ vespalib::string expected(vespalib::make_vespa_string("%d", _replyCount));
+ SimpleReply& simpleReply(static_cast<SimpleReply&>(*reply));
+ if (simpleReply.getValue() != expected) {
+ std::stringstream ss;
+ ss << "Received out-of-sequence reply! Expected "
+ << expected
+ << ", but got "
+ << simpleReply.getValue();
+ LOG(warning, "%s", ss.str().c_str());
+ if (_failure.empty()) {
+ _failure = ss.str();
+ }
+ }
+ }
+ ++_replyCount;
+ lock.broadcast();
+ }
+ void waitUntilDone(int waitForCount) const
+ {
+ vespalib::MonitorGuard lock(_mon);
+ while (_replyCount < waitForCount) {
+ lock.wait(1000);
+ }
+ }
+ const std::string& getFailure() const { return _failure; }
+};
+
+int
+Test::Main()
+{
+ TEST_INIT("messageordering_test");
+
+ Slobrok slobrok;
+ TestServer srcNet(Identity("test/src"), getRouting(), slobrok);
+ TestServer dstNet(Identity("test/dst"), getRouting(), slobrok);
+
+ VerifyReplyReceptor src;
+ MultiReceptor dst;
+
+ SourceSessionParams ssp;
+ ssp.setThrottlePolicy(IThrottlePolicy::SP());
+ ssp.setTimeout(400);
+ SourceSession::UP ss = srcNet.mb.createSourceSession(src, ssp);
+ DestinationSession::UP ds = dstNet.mb.createDestinationSession("session", true, dst);
+ ASSERT_EQUAL(400u, ssp.getTimeout());
+
+ // wait for slobrok registration
+ ASSERT_TRUE(srcNet.waitSlobrok("test/dst/session"));
+
+ // same message id for all messages in order to guarantee ordering
+ int commonMessageId = 42;
+
+ // send messages on client
+ const int messageCount = 10000;
+ for (int i = 0; i < messageCount; ++i) {
+ vespalib::string str(vespalib::make_vespa_string("%d", i));
+ //FastOS_Thread::Sleep(1);
+ SimpleMessage::UP msg(new SimpleMessage(str, true, commonMessageId));
+ msg->getTrace().setLevel(9);
+ //LOG(debug, "Sending message %p for %d", msg.get(), i);
+ ASSERT_EQUAL(uint32_t(ErrorCode::NONE),
+ ss->send(std::move(msg), "test").getError().getCode());
+ }
+ src.waitUntilDone(messageCount);
+
+ ASSERT_EQUAL(std::string(), src.getFailure());
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/messenger/.gitignore b/messagebus/src/tests/messenger/.gitignore
new file mode 100644
index 00000000000..a7a74282fc7
--- /dev/null
+++ b/messagebus/src/tests/messenger/.gitignore
@@ -0,0 +1,3 @@
+/.depend
+/Makefile
+messagebus_messenger_test_app
diff --git a/messagebus/src/tests/messenger/CMakeLists.txt b/messagebus/src/tests/messenger/CMakeLists.txt
new file mode 100644
index 00000000000..6dfab750bf4
--- /dev/null
+++ b/messagebus/src/tests/messenger/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_messenger_test_app
+ SOURCES
+ messenger.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_messenger_test_app COMMAND messagebus_messenger_test_app)
diff --git a/messagebus/src/tests/messenger/DESC b/messagebus/src/tests/messenger/DESC
new file mode 100644
index 00000000000..f4b52840a14
--- /dev/null
+++ b/messagebus/src/tests/messenger/DESC
@@ -0,0 +1 @@
+messenger test. Take a look at messenger.cpp for details.
diff --git a/messagebus/src/tests/messenger/FILES b/messagebus/src/tests/messenger/FILES
new file mode 100644
index 00000000000..620b105632e
--- /dev/null
+++ b/messagebus/src/tests/messenger/FILES
@@ -0,0 +1 @@
+messenger.cpp
diff --git a/messagebus/src/tests/messenger/messenger.cpp b/messagebus/src/tests/messenger/messenger.cpp
new file mode 100644
index 00000000000..a736814aa3d
--- /dev/null
+++ b/messagebus/src/tests/messenger/messenger.cpp
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("messagebus_test");
+
+#include <vespa/messagebus/messenger.h>
+#include <vespa/vespalib/util/barrier.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+class ThrowException : public Messenger::ITask {
+public:
+ void run() {
+ throw std::exception();
+ }
+
+ uint8_t priority() const {
+ return 0;
+ }
+};
+
+class BarrierTask : public Messenger::ITask {
+private:
+ vespalib::Barrier &_barrier;
+
+public:
+ BarrierTask(vespalib::Barrier &barrier)
+ : _barrier(barrier)
+ {
+ // empty
+ }
+
+ void run() {
+ _barrier.await();
+ }
+
+ uint8_t priority() const {
+ return 0;
+ }
+};
+
+int
+Test::Main()
+{
+ TEST_INIT("messenger_test");
+
+ Messenger msn;
+ msn.start();
+
+ vespalib::Barrier barrier(2);
+ msn.enqueue(Messenger::ITask::UP(new ThrowException()));
+ msn.enqueue(Messenger::ITask::UP(new BarrierTask(barrier)));
+
+ barrier.await();
+ ASSERT_TRUE(msn.isEmpty());
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/oos/.gitignore b/messagebus/src/tests/oos/.gitignore
new file mode 100644
index 00000000000..a4771a9176b
--- /dev/null
+++ b/messagebus/src/tests/oos/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+oos_test
+messagebus_oos_test_app
diff --git a/messagebus/src/tests/oos/CMakeLists.txt b/messagebus/src/tests/oos/CMakeLists.txt
new file mode 100644
index 00000000000..1d037ef6f6e
--- /dev/null
+++ b/messagebus/src/tests/oos/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_oos_test_app
+ SOURCES
+ oos.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_oos_test_app NO_VALGRIND COMMAND messagebus_oos_test_app)
diff --git a/messagebus/src/tests/oos/DESC b/messagebus/src/tests/oos/DESC
new file mode 100644
index 00000000000..16cd7a2f30d
--- /dev/null
+++ b/messagebus/src/tests/oos/DESC
@@ -0,0 +1 @@
+oos test. Take a look at oos.cpp for details.
diff --git a/messagebus/src/tests/oos/FILES b/messagebus/src/tests/oos/FILES
new file mode 100644
index 00000000000..08cf509e1fd
--- /dev/null
+++ b/messagebus/src/tests/oos/FILES
@@ -0,0 +1 @@
+oos.cpp
diff --git a/messagebus/src/tests/oos/oos.cpp b/messagebus/src/tests/oos/oos.cpp
new file mode 100644
index 00000000000..0ff93cecd7d
--- /dev/null
+++ b/messagebus/src/tests/oos/oos.cpp
@@ -0,0 +1,231 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("oos_test");
+
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/oosserver.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+struct Handler : public IMessageHandler
+{
+ DestinationSession::UP session;
+ Handler(MessageBus &mb) : session() {
+ session = mb.createDestinationSession("session", true, *this);
+ }
+ ~Handler() {
+ session.reset();
+ }
+ virtual void handleMessage(Message::UP msg) {
+ session->acknowledge(std::move(msg));
+ }
+};
+
+
+class Test : public vespalib::TestApp {
+private:
+ SourceSession::UP _session;
+ RoutableQueue _handler;
+
+ bool checkError(const string &dst, uint32_t error);
+
+public:
+ Test();
+ int Main();
+};
+
+TEST_APPHOOK(Test);
+
+Test::Test() :
+ _session(),
+ _handler()
+{
+ // empty
+}
+
+bool
+Test::checkError(const string &dst, uint32_t error)
+{
+ if (!EXPECT_TRUE(_session.get() != NULL)) {
+ return false;
+ }
+ Message::UP msg(new SimpleMessage("msg"));
+ msg->getTrace().setLevel(9);
+ if (!EXPECT_TRUE(_session->send(std::move(msg), Route::parse(dst)).isAccepted())) {
+ return false;
+ }
+ Routable::UP reply = _handler.dequeue(10000);
+ if (!EXPECT_TRUE(reply.get() != NULL)) {
+ return false;
+ }
+ if (!EXPECT_TRUE(reply->isReply())) {
+ return false;
+ }
+ Reply &ref = static_cast<Reply&>(*reply);
+ printf("%s", ref.getTrace().toString().c_str());
+ if (error == ErrorCode::NONE) {
+ if (!EXPECT_TRUE(!ref.hasErrors())) {
+ return false;
+ }
+ } else {
+ if (!EXPECT_TRUE(ref.hasErrors())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(error, ref.getError(0).getCode())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("oos_test");
+
+ Slobrok slobrok;
+ TestServer src(Identity(""), RoutingSpec(), slobrok, "oos/*");
+ TestServer dst1(Identity("dst1"), RoutingSpec(), slobrok);
+ TestServer dst2(Identity("dst2"), RoutingSpec(), slobrok);
+ TestServer dst3(Identity("dst3"), RoutingSpec(), slobrok);
+ TestServer dst4(Identity("dst4"), RoutingSpec(), slobrok);
+ TestServer dst5(Identity("dst5"), RoutingSpec(), slobrok);
+ Handler h1(dst1.mb);
+ Handler h2(dst2.mb);
+ Handler h3(dst3.mb);
+ Handler h4(dst4.mb);
+ Handler h5(dst5.mb);
+ EXPECT_TRUE(src.waitSlobrok("*/session", 5));
+
+ _session = src.mb.createSourceSession(_handler);
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::NONE));
+ TEST_FLUSH();
+ OOSServer oosServer(slobrok, "oos/1", OOSState()
+ .add("dst2/session")
+ .add("dst3/session"));
+ EXPECT_TRUE(src.waitSlobrok("oos/*", 1));
+ EXPECT_TRUE(src.waitState(OOSState()
+ .add("dst2/session")
+ .add("dst3/session")));
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::NONE)); // test 9
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS)); // return without reply?!?
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::NONE));
+ TEST_FLUSH();
+ oosServer.setState(OOSState()
+ .add("dst2/session"));
+ EXPECT_TRUE(src.waitState(OOSState()
+ .add("dst2/session", true)
+ .add("dst3/session", false)));
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::NONE));
+ TEST_FLUSH();
+ {
+ OOSServer oosServer2(slobrok, "oos/2", OOSState()
+ .add("dst4/session")
+ .add("dst5/session"));
+ EXPECT_TRUE(src.waitSlobrok("oos/*", 2));
+ EXPECT_TRUE(src.waitState(OOSState()
+ .add("dst2/session")
+ .add("dst4/session")
+ .add("dst5/session")));
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::SERVICE_OOS));
+ TEST_FLUSH();
+ }
+ EXPECT_TRUE(src.waitSlobrok("oos/*", 1));
+ EXPECT_TRUE(src.waitState(OOSState()
+ .add("dst1/session", false)
+ .add("dst2/session", true)
+ .add("dst3/session", false)
+ .add("dst4/session", false)
+ .add("dst5/session", false)));
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::NONE));
+ TEST_FLUSH();
+ {
+ OOSServer oosServer3(slobrok, "oos/3", OOSState()
+ .add("dst2/session")
+ .add("dst4/session"));
+ OOSServer oosServer4(slobrok, "oos/4", OOSState()
+ .add("dst2/session")
+ .add("dst3/session")
+ .add("dst5/session"));
+ EXPECT_TRUE(src.waitSlobrok("oos/*", 3));
+ EXPECT_TRUE(src.waitState(OOSState()
+ .add("dst2/session")
+ .add("dst3/session")
+ .add("dst4/session")
+ .add("dst5/session")));
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::SERVICE_OOS));
+ TEST_FLUSH();
+ oosServer3.setState(OOSState()
+ .add("dst2/session"));
+ oosServer4.setState(OOSState()
+ .add("dst1/session"));
+ EXPECT_TRUE(src.waitState(OOSState()
+ .add("dst1/session", true)
+ .add("dst2/session", true)
+ .add("dst3/session", false)
+ .add("dst4/session", false)
+ .add("dst5/session", false)));
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::NONE));
+ TEST_FLUSH();
+ }
+ EXPECT_TRUE(src.waitSlobrok("oos/*", 1));
+ EXPECT_TRUE(src.waitState(OOSState()
+ .add("dst1/session", false)
+ .add("dst2/session", true)
+ .add("dst3/session", false)
+ .add("dst4/session", false)
+ .add("dst5/session", false)));
+ EXPECT_TRUE(checkError("dst1/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS));
+ EXPECT_TRUE(checkError("dst3/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst4/session", ErrorCode::NONE));
+ EXPECT_TRUE(checkError("dst5/session", ErrorCode::NONE));
+
+ h2.session.reset();
+ EXPECT_TRUE(src.waitSlobrok("*/session", 4));
+ EXPECT_TRUE(checkError("dst2/session", ErrorCode::SERVICE_OOS));
+
+ _session.reset();
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/oospolicy/.gitignore b/messagebus/src/tests/oospolicy/.gitignore
new file mode 100644
index 00000000000..3bd6e47e0bc
--- /dev/null
+++ b/messagebus/src/tests/oospolicy/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+oospolicy_test
diff --git a/messagebus/src/tests/protocolrepository/.gitignore b/messagebus/src/tests/protocolrepository/.gitignore
new file mode 100644
index 00000000000..824d12fc43f
--- /dev/null
+++ b/messagebus/src/tests/protocolrepository/.gitignore
@@ -0,0 +1,3 @@
+/.depend
+/Makefile
+messagebus_protocolrepository_test_app
diff --git a/messagebus/src/tests/protocolrepository/CMakeLists.txt b/messagebus/src/tests/protocolrepository/CMakeLists.txt
new file mode 100644
index 00000000000..68156e5eee9
--- /dev/null
+++ b/messagebus/src/tests/protocolrepository/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_protocolrepository_test_app
+ SOURCES
+ protocolrepository.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_protocolrepository_test_app NO_VALGRIND COMMAND messagebus_protocolrepository_test_app)
diff --git a/messagebus/src/tests/protocolrepository/DESC b/messagebus/src/tests/protocolrepository/DESC
new file mode 100644
index 00000000000..98e3dc9ef6e
--- /dev/null
+++ b/messagebus/src/tests/protocolrepository/DESC
@@ -0,0 +1 @@
+protocolrepository test. Take a look at protocolrepository.cpp for details.
diff --git a/messagebus/src/tests/protocolrepository/FILES b/messagebus/src/tests/protocolrepository/FILES
new file mode 100644
index 00000000000..2fc199b4aef
--- /dev/null
+++ b/messagebus/src/tests/protocolrepository/FILES
@@ -0,0 +1 @@
+protocolrepository.cpp
diff --git a/messagebus/src/tests/protocolrepository/protocolrepository.cpp b/messagebus/src/tests/protocolrepository/protocolrepository.cpp
new file mode 100644
index 00000000000..e1b1d1402dd
--- /dev/null
+++ b/messagebus/src/tests/protocolrepository/protocolrepository.cpp
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("protocolrepository_test");
+
+#include <vespa/messagebus/protocolrepository.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+class TestProtocol : public IProtocol {
+private:
+ const string _name;
+
+public:
+
+ TestProtocol(const string &name)
+ : _name(name)
+ {
+ // empty
+ }
+
+ const string &
+ getName() const
+ {
+ return _name;
+ }
+
+ IRoutingPolicy::UP
+ createPolicy(const string &name, const string &param) const
+ {
+ (void)name;
+ (void)param;
+ throw std::exception();
+ }
+
+ Blob
+ encode(const vespalib::Version &version, const Routable &routable) const
+ {
+ (void)version;
+ (void)routable;
+ throw std::exception();
+ }
+
+ Routable::UP
+ decode(const vespalib::Version &version, BlobRef data) const
+ {
+ (void)version;
+ (void)data;
+ throw std::exception();
+ }
+};
+
+int
+Test::Main()
+{
+ TEST_INIT("protocolrepository_test");
+
+ ProtocolRepository repo;
+ IProtocol::SP prev;
+ prev = repo.putProtocol(IProtocol::SP(new TestProtocol("foo")));
+ ASSERT_TRUE(prev.get() == NULL);
+
+ IRoutingPolicy::SP policy = repo.getRoutingPolicy("foo", "bar", "baz");
+ prev = repo.putProtocol(IProtocol::SP(new TestProtocol("foo")));
+ ASSERT_TRUE(prev.get() != NULL);
+ ASSERT_NOT_EQUAL(prev.get(), repo.getProtocol("foo").get());
+
+ policy = repo.getRoutingPolicy("foo", "bar", "baz");
+ ASSERT_TRUE(policy.get() == NULL);
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/queue/.gitignore b/messagebus/src/tests/queue/.gitignore
new file mode 100644
index 00000000000..7b5371ea913
--- /dev/null
+++ b/messagebus/src/tests/queue/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+queue_test
+messagebus_queue_test_app
diff --git a/messagebus/src/tests/queue/CMakeLists.txt b/messagebus/src/tests/queue/CMakeLists.txt
new file mode 100644
index 00000000000..c330421ac8a
--- /dev/null
+++ b/messagebus/src/tests/queue/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_queue_test_app
+ SOURCES
+ queue.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_queue_test_app COMMAND messagebus_queue_test_app)
diff --git a/messagebus/src/tests/queue/DESC b/messagebus/src/tests/queue/DESC
new file mode 100644
index 00000000000..1d795755700
--- /dev/null
+++ b/messagebus/src/tests/queue/DESC
@@ -0,0 +1 @@
+queue test. Take a look at queue.cpp for details.
diff --git a/messagebus/src/tests/queue/FILES b/messagebus/src/tests/queue/FILES
new file mode 100644
index 00000000000..6fb01ca3173
--- /dev/null
+++ b/messagebus/src/tests/queue/FILES
@@ -0,0 +1 @@
+queue.cpp
diff --git a/messagebus/src/tests/queue/queue.cpp b/messagebus/src/tests/queue/queue.cpp
new file mode 100644
index 00000000000..78bf09e2c48
--- /dev/null
+++ b/messagebus/src/tests/queue/queue.cpp
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("queue_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/queue.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("queue_test");
+ Queue<int> q;
+ EXPECT_TRUE(q.size() == 0);
+ q.push(1);
+ EXPECT_TRUE(q.size() == 1);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(2);
+ EXPECT_TRUE(q.size() == 2);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(3);
+ EXPECT_TRUE(q.size() == 3);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(4);
+ EXPECT_TRUE(q.size() == 4);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(5);
+ EXPECT_TRUE(q.size() == 5);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(6);
+ EXPECT_TRUE(q.size() == 6);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(7);
+ EXPECT_TRUE(q.size() == 7);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(8);
+ EXPECT_TRUE(q.size() == 8);
+ EXPECT_TRUE(q.front() == 1);
+ q.push(9);
+ EXPECT_TRUE(q.size() == 9);
+ EXPECT_TRUE(q.front() == 1);
+ q.pop();
+ EXPECT_TRUE(q.size() == 8);
+ EXPECT_TRUE(q.front() == 2);
+ q.pop();
+ EXPECT_TRUE(q.size() == 7);
+ EXPECT_TRUE(q.front() == 3);
+ q.pop();
+ EXPECT_TRUE(q.size() == 6);
+ EXPECT_TRUE(q.front() == 4);
+ q.push(1);
+ EXPECT_TRUE(q.size() == 7);
+ EXPECT_TRUE(q.front() == 4);
+ q.push(2);
+ EXPECT_TRUE(q.size() == 8);
+ EXPECT_TRUE(q.front() == 4);
+ q.push(3);
+ EXPECT_TRUE(q.size() == 9);
+ EXPECT_TRUE(q.front() == 4);
+ q.pop();
+ EXPECT_TRUE(q.size() == 8);
+ EXPECT_TRUE(q.front() == 5);
+ q.pop();
+ EXPECT_TRUE(q.size() == 7);
+ EXPECT_TRUE(q.front() == 6);
+ q.pop();
+ EXPECT_TRUE(q.size() == 6);
+ EXPECT_TRUE(q.front() == 7);
+ q.pop();
+ EXPECT_TRUE(q.size() == 5);
+ EXPECT_TRUE(q.front() == 8);
+ q.pop();
+ EXPECT_TRUE(q.size() == 4);
+ EXPECT_TRUE(q.front() == 9);
+ q.pop();
+ EXPECT_TRUE(q.size() == 3);
+ EXPECT_TRUE(q.front() == 1);
+ q.pop();
+ EXPECT_TRUE(q.size() == 2);
+ EXPECT_TRUE(q.front() == 2);
+ q.pop();
+ EXPECT_TRUE(q.size() == 1);
+ EXPECT_TRUE(q.front() == 3);
+ q.pop();
+ EXPECT_TRUE(q.size() == 0);
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/replygate/.gitignore b/messagebus/src/tests/replygate/.gitignore
new file mode 100644
index 00000000000..65ec6c9a372
--- /dev/null
+++ b/messagebus/src/tests/replygate/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+replygate_test
+messagebus_replygate_test_app
diff --git a/messagebus/src/tests/replygate/CMakeLists.txt b/messagebus/src/tests/replygate/CMakeLists.txt
new file mode 100644
index 00000000000..8844ef862dd
--- /dev/null
+++ b/messagebus/src/tests/replygate/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_replygate_test_app
+ SOURCES
+ replygate.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_replygate_test_app COMMAND messagebus_replygate_test_app)
diff --git a/messagebus/src/tests/replygate/DESC b/messagebus/src/tests/replygate/DESC
new file mode 100644
index 00000000000..2bb86cc490b
--- /dev/null
+++ b/messagebus/src/tests/replygate/DESC
@@ -0,0 +1 @@
+replygate test. Take a look at replygate.cpp for details.
diff --git a/messagebus/src/tests/replygate/FILES b/messagebus/src/tests/replygate/FILES
new file mode 100644
index 00000000000..3169994c2ca
--- /dev/null
+++ b/messagebus/src/tests/replygate/FILES
@@ -0,0 +1 @@
+replygate.cpp
diff --git a/messagebus/src/tests/replygate/replygate.cpp b/messagebus/src/tests/replygate/replygate.cpp
new file mode 100644
index 00000000000..09368da3974
--- /dev/null
+++ b/messagebus/src/tests/replygate/replygate.cpp
@@ -0,0 +1,91 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("replygate_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/imessagehandler.h>
+#include <vespa/messagebus/replygate.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+struct MyGate : public ReplyGate
+{
+ static int ctorCnt;
+ static int dtorCnt;
+ MyGate(IMessageHandler &sender) : ReplyGate(sender) {
+ ++ctorCnt;
+ }
+ virtual ~MyGate() {
+ ++dtorCnt;
+ }
+};
+int MyGate::ctorCnt = 0;
+int MyGate::dtorCnt = 0;
+
+struct MyReply : public EmptyReply
+{
+ static int ctorCnt;
+ static int dtorCnt;
+ MyReply() : EmptyReply() {
+ ++ctorCnt;
+ }
+ virtual ~MyReply() {
+ ++dtorCnt;
+ }
+};
+int MyReply::ctorCnt = 0;
+int MyReply::dtorCnt = 0;
+
+struct MySender : public IMessageHandler
+{
+ // giving a sync reply here is against the API contract, but it is
+ // ok for testing.
+ virtual void handleMessage(Message::UP msg) {
+ Reply::UP reply(new MyReply());
+ msg->swapState(*reply);
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+ }
+};
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("replygate_test");
+ {
+ RoutableQueue q;
+ MySender sender;
+ MyGate *gate = new MyGate(sender);
+ {
+ Message::UP msg(new SimpleMessage("test"));
+ msg->pushHandler(q);
+ gate->handleMessage(std::move(msg));
+ }
+ EXPECT_TRUE(q.size() == 1);
+ EXPECT_TRUE(MyReply::ctorCnt == 1);
+ EXPECT_TRUE(MyReply::dtorCnt == 0);
+ gate->close();
+ {
+ Message::UP msg(new SimpleMessage("test"));
+ msg->pushHandler(q);
+ gate->handleMessage(std::move(msg));
+ }
+ EXPECT_TRUE(q.size() == 1);
+ EXPECT_TRUE(MyReply::ctorCnt == 2);
+ EXPECT_TRUE(MyReply::dtorCnt == 1);
+ EXPECT_TRUE(MyGate::ctorCnt == 1);
+ EXPECT_TRUE(MyGate::dtorCnt == 0);
+ gate->subRef();
+ EXPECT_TRUE(MyGate::ctorCnt == 1);
+ EXPECT_TRUE(MyGate::dtorCnt == 1);
+ }
+ EXPECT_TRUE(MyReply::ctorCnt == 2);
+ EXPECT_TRUE(MyReply::dtorCnt == 2);
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/replyset/.gitignore b/messagebus/src/tests/replyset/.gitignore
new file mode 100644
index 00000000000..0efd61559c9
--- /dev/null
+++ b/messagebus/src/tests/replyset/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+replyset_test
diff --git a/messagebus/src/tests/resender/.gitignore b/messagebus/src/tests/resender/.gitignore
new file mode 100644
index 00000000000..ec4ca62b805
--- /dev/null
+++ b/messagebus/src/tests/resender/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+resender_test
+messagebus_resender_test_app
diff --git a/messagebus/src/tests/resender/CMakeLists.txt b/messagebus/src/tests/resender/CMakeLists.txt
new file mode 100644
index 00000000000..b253e582af8
--- /dev/null
+++ b/messagebus/src/tests/resender/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_resender_test_app
+ SOURCES
+ resender.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_resender_test_app COMMAND messagebus_resender_test_app)
diff --git a/messagebus/src/tests/resender/DESC b/messagebus/src/tests/resender/DESC
new file mode 100644
index 00000000000..0b234bf57b9
--- /dev/null
+++ b/messagebus/src/tests/resender/DESC
@@ -0,0 +1 @@
+resender test. Take a look at resender.cpp for details.
diff --git a/messagebus/src/tests/resender/FILES b/messagebus/src/tests/resender/FILES
new file mode 100644
index 00000000000..16b7c7fe76b
--- /dev/null
+++ b/messagebus/src/tests/resender/FILES
@@ -0,0 +1 @@
+resender.cpp
diff --git a/messagebus/src/tests/resender/resender.cpp b/messagebus/src/tests/resender/resender.cpp
new file mode 100644
index 00000000000..a067616d1ba
--- /dev/null
+++ b/messagebus/src/tests/resender/resender.cpp
@@ -0,0 +1,310 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routing_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routing/errordirective.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/testlib/custompolicy.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class StringList : public std::vector<string> {
+public:
+ StringList &add(const string &str);
+};
+
+StringList &
+StringList::add(const string &str)
+{
+ std::vector<string>::push_back(str); return *this;
+}
+
+static const double GET_MESSAGE_TIMEOUT = 60.0;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class TestData {
+public:
+ Slobrok _slobrok;
+ RetryTransientErrorsPolicy::SP _retryPolicy;
+ TestServer _srcServer;
+ SourceSession::UP _srcSession;
+ Receptor _srcHandler;
+ TestServer _dstServer;
+ DestinationSession::UP _dstSession;
+ Receptor _dstHandler;
+
+public:
+ TestData();
+ bool start();
+};
+
+class Test : public vespalib::TestApp {
+private:
+ Message::UP createMessage(const string &msg);
+ void replyFromDestination(TestData &data, Message::UP msg, uint32_t errorCode, double retryDelay);
+
+public:
+ int Main();
+ void testRetryTag(TestData &data);
+ void testRetryEnabledTag(TestData &data);
+ void testTransientError(TestData &data);
+ void testFatalError(TestData &data);
+ void testDisableRetry(TestData &data);
+ void testRetryDelay(TestData &data);
+ void testRequestRetryDelay(TestData &data);
+};
+
+TEST_APPHOOK(Test);
+
+TestData::TestData() :
+ _slobrok(),
+ _retryPolicy(new RetryTransientErrorsPolicy()),
+ _srcServer(MessageBusParams().setRetryPolicy(_retryPolicy).addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setSlobrokConfig(_slobrok.config())),
+ _srcSession(),
+ _srcHandler(),
+ _dstServer(MessageBusParams().addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setIdentity(Identity("dst")).setSlobrokConfig(_slobrok.config())),
+ _dstSession(),
+ _dstHandler()
+{
+ // empty
+}
+
+bool
+TestData::start()
+{
+ _srcSession = _srcServer.mb.createSourceSession(SourceSessionParams().setReplyHandler(_srcHandler));
+ if (_srcSession.get() == NULL) {
+ return false;
+ }
+ _dstSession = _dstServer.mb.createDestinationSession(DestinationSessionParams().setName("session").setMessageHandler(_dstHandler));
+ if (_dstSession.get() == NULL) {
+ return false;
+ }
+ if (!_srcServer.waitSlobrok("dst/session", 1u)) {
+ return false;
+ }
+ return true;
+}
+
+Message::UP
+Test::createMessage(const string &msg)
+{
+ Message::UP ret(new SimpleMessage(msg));
+ ret->getTrace().setLevel(9);
+ return ret;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("resender_test");
+
+ TestData data;
+ ASSERT_TRUE(data.start());
+
+ testRetryTag(data); TEST_FLUSH();
+ testRetryEnabledTag(data); TEST_FLUSH();
+ testTransientError(data); TEST_FLUSH();
+ testFatalError(data); TEST_FLUSH();
+ testDisableRetry(data); TEST_FLUSH();
+ testRetryDelay(data); TEST_FLUSH();
+ testRequestRetryDelay(data); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+void
+Test::replyFromDestination(TestData &data, Message::UP msg, uint32_t errorCode, double retryDelay)
+{
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ if (errorCode != ErrorCode::NONE) {
+ reply->addError(Error(errorCode, "err"));
+ }
+ reply->setRetryDelay(retryDelay);
+ data._dstSession->reply(std::move(reply));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test::testRetryTag(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ for (uint32_t i = 0; i < 5; ++i) {
+ EXPECT_EQUAL(i, msg->getRetry());
+ EXPECT_EQUAL(true, msg->getRetryEnabled());
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_TRANSIENT_ERROR, 0);
+ msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ }
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(!reply->hasErrors());
+ msg = data._dstHandler.getMessage(0);
+ EXPECT_TRUE(msg.get() == NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+}
+
+void
+Test::testRetryEnabledTag(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ Message::UP msg = createMessage("msg");
+ msg->setRetryEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(std::move(msg), Route::parse("dst/session")).isAccepted());
+ msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ EXPECT_EQUAL(false, msg->getRetryEnabled());
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_TRANSIENT_ERROR, 0);
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(reply->hasErrors());
+ msg = data._dstHandler.getMessage(0);
+ EXPECT_TRUE(msg.get() == NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+}
+
+void
+Test::testTransientError(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_TRANSIENT_ERROR, 0);
+ msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_FATAL_ERROR, 0);
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(reply->hasFatalErrors());
+ msg = data._dstHandler.getMessage(0);
+ EXPECT_TRUE(msg.get() == NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+}
+
+void
+Test::testFatalError(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_FATAL_ERROR, 0);
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(reply->hasFatalErrors());
+ msg = data._dstHandler.getMessage(0);
+ EXPECT_TRUE(msg.get() == NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+}
+
+void
+Test::testDisableRetry(TestData &data)
+{
+ data._retryPolicy->setEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_TRANSIENT_ERROR, 0);
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(reply->hasErrors());
+ EXPECT_TRUE(!reply->hasFatalErrors());
+ msg = data._dstHandler.getMessage(0);
+ EXPECT_TRUE(msg.get() == NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+}
+
+void
+Test::testRetryDelay(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ data._retryPolicy->setBaseDelay(0.01);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ for (uint32_t i = 0; i < 5; ++i) {
+ EXPECT_EQUAL(i, msg->getRetry());
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_TRANSIENT_ERROR, -1);
+ msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ }
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_FATAL_ERROR, 0);
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(reply->hasFatalErrors());
+ msg = data._dstHandler.getMessage(0);
+ EXPECT_TRUE(msg.get() == NULL);
+
+ string trace = reply->getTrace().toString();
+ printf("%s", trace.c_str());
+ EXPECT_TRUE(trace.find("retry 1 in 0.01") != string::npos);
+ EXPECT_TRUE(trace.find("retry 2 in 0.02") != string::npos);
+ EXPECT_TRUE(trace.find("retry 3 in 0.03") != string::npos);
+ EXPECT_TRUE(trace.find("retry 4 in 0.04") != string::npos);
+ EXPECT_TRUE(trace.find("retry 5 in 0.05") != string::npos);
+}
+
+void
+Test::testRequestRetryDelay(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ data._retryPolicy->setBaseDelay(1);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ for (uint32_t i = 0; i < 5; ++i) {
+ EXPECT_EQUAL(i, msg->getRetry());
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_TRANSIENT_ERROR, i / 50.0);
+ msg = data._dstHandler.getMessage(GET_MESSAGE_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ }
+ replyFromDestination(data, std::move(msg), ErrorCode::APP_FATAL_ERROR, 0);
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_TRUE(reply->hasFatalErrors());
+ msg = data._dstHandler.getMessage(0);
+ EXPECT_TRUE(msg.get() == NULL);
+
+ string trace = reply->getTrace().toString();
+ printf("%s", trace.c_str());
+ EXPECT_TRUE(trace.find("retry 1 in 0") != string::npos);
+ EXPECT_TRUE(trace.find("retry 2 in 0.02") != string::npos);
+ EXPECT_TRUE(trace.find("retry 3 in 0.04") != string::npos);
+ EXPECT_TRUE(trace.find("retry 4 in 0.06") != string::npos);
+ EXPECT_TRUE(trace.find("retry 5 in 0.08") != string::npos);
+}
+
diff --git a/messagebus/src/tests/result/.gitignore b/messagebus/src/tests/result/.gitignore
new file mode 100644
index 00000000000..2f5999ef7d0
--- /dev/null
+++ b/messagebus/src/tests/result/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+result_test
+messagebus_result_test_app
diff --git a/messagebus/src/tests/result/CMakeLists.txt b/messagebus/src/tests/result/CMakeLists.txt
new file mode 100644
index 00000000000..bd1ed96ab5f
--- /dev/null
+++ b/messagebus/src/tests/result/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_result_test_app
+ SOURCES
+ result.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_result_test_app COMMAND messagebus_result_test_app)
diff --git a/messagebus/src/tests/result/DESC b/messagebus/src/tests/result/DESC
new file mode 100644
index 00000000000..8192ff0830c
--- /dev/null
+++ b/messagebus/src/tests/result/DESC
@@ -0,0 +1 @@
+Simple test of the Result class.
diff --git a/messagebus/src/tests/result/FILES b/messagebus/src/tests/result/FILES
new file mode 100644
index 00000000000..55a888fd93f
--- /dev/null
+++ b/messagebus/src/tests/result/FILES
@@ -0,0 +1 @@
+result.cpp
diff --git a/messagebus/src/tests/result/result.cpp b/messagebus/src/tests/result/result.cpp
new file mode 100644
index 00000000000..1b081f5fc9e
--- /dev/null
+++ b/messagebus/src/tests/result/result.cpp
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("result_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/result.h>
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+
+using namespace mbus;
+
+struct MyMessage : public SimpleMessage
+{
+ static int ctorCnt;
+ static int dtorCnt;
+ MyMessage(const string &str) : SimpleMessage(str) {
+ ++ctorCnt;
+ }
+ virtual ~MyMessage() {
+ ++dtorCnt;
+ }
+};
+int MyMessage::ctorCnt = 0;
+int MyMessage::dtorCnt = 0;
+
+struct Test : public vespalib::TestApp
+{
+ Result sendOk(Message::UP msg);
+ Result sendFail(Message::UP msg);
+ int Main();
+};
+
+Result
+Test::sendOk(Message::UP msg) {
+ (void) msg;
+ return Result();
+}
+
+Result
+Test::sendFail(Message::UP msg) {
+ return Result(Error(ErrorCode::FATAL_ERROR, "error"), std::move(msg));
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("result_test");
+ { // test accepted
+ Message::UP msg(new MyMessage("test"));
+ Result res = sendOk(std::move(msg));
+ EXPECT_TRUE(msg.get() == 0);
+ EXPECT_TRUE(res.isAccepted());
+ EXPECT_TRUE(res.getError().getCode() == ErrorCode::NONE);
+ EXPECT_TRUE(res.getError().getMessage() == "");
+ Message::UP back = res.getMessage();
+ EXPECT_TRUE(back.get() == 0);
+ }
+ { // test failed
+ Message::UP msg(new MyMessage("test"));
+ Message *raw = msg.get();
+ EXPECT_TRUE(raw != 0);
+ Result res = sendFail(std::move(msg));
+ EXPECT_TRUE(msg.get() == 0);
+ EXPECT_TRUE(!res.isAccepted());
+ EXPECT_TRUE(res.getError().getCode() == ErrorCode::FATAL_ERROR);
+ EXPECT_TRUE(res.getError().getMessage() == "error");
+ Message::UP back = res.getMessage();
+ EXPECT_TRUE(back.get() == raw);
+ }
+ EXPECT_TRUE(MyMessage::ctorCnt == 2);
+ EXPECT_TRUE(MyMessage::dtorCnt == 2);
+ TEST_DONE();
+}
+
+TEST_APPHOOK(Test);
diff --git a/messagebus/src/tests/retrypolicy/.gitignore b/messagebus/src/tests/retrypolicy/.gitignore
new file mode 100644
index 00000000000..672e4f58b21
--- /dev/null
+++ b/messagebus/src/tests/retrypolicy/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+retrypolicy_test
+messagebus_retrypolicy_test_app
diff --git a/messagebus/src/tests/retrypolicy/CMakeLists.txt b/messagebus/src/tests/retrypolicy/CMakeLists.txt
new file mode 100644
index 00000000000..2a2a2a20e6e
--- /dev/null
+++ b/messagebus/src/tests/retrypolicy/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_retrypolicy_test_app
+ SOURCES
+ retrypolicy.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_retrypolicy_test_app COMMAND messagebus_retrypolicy_test_app)
diff --git a/messagebus/src/tests/retrypolicy/DESC b/messagebus/src/tests/retrypolicy/DESC
new file mode 100644
index 00000000000..0037ca01c5c
--- /dev/null
+++ b/messagebus/src/tests/retrypolicy/DESC
@@ -0,0 +1 @@
+retrypolicy test. Take a look at retrypolicy.cpp for details.
diff --git a/messagebus/src/tests/retrypolicy/FILES b/messagebus/src/tests/retrypolicy/FILES
new file mode 100644
index 00000000000..11a520524fb
--- /dev/null
+++ b/messagebus/src/tests/retrypolicy/FILES
@@ -0,0 +1 @@
+retrypolicy.cpp
diff --git a/messagebus/src/tests/retrypolicy/retrypolicy.cpp b/messagebus/src/tests/retrypolicy/retrypolicy.cpp
new file mode 100644
index 00000000000..5426a1123cd
--- /dev/null
+++ b/messagebus/src/tests/retrypolicy/retrypolicy.cpp
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("retrypolicy_test");
+
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("retrypolicy_test");
+
+ RetryTransientErrorsPolicy policy;
+ for (uint32_t i = 0; i < 5; ++i) {
+ double delay = i / 3.0;
+ policy.setBaseDelay(delay);
+ for (uint32_t j = 0; j < 5; ++j) {
+ EXPECT_EQUAL((int)(j * delay), (int)policy.getRetryDelay(j));
+ }
+ for (uint32_t j = ErrorCode::NONE; j < ErrorCode::ERROR_LIMIT; ++j) {
+ policy.setEnabled(true);
+ if (j < ErrorCode::FATAL_ERROR) {
+ EXPECT_TRUE(policy.canRetry(j));
+ } else {
+ EXPECT_TRUE(!policy.canRetry(j));
+ }
+ policy.setEnabled(false);
+ EXPECT_TRUE(!policy.canRetry(j));
+ }
+ }
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/routable/.gitignore b/messagebus/src/tests/routable/.gitignore
new file mode 100644
index 00000000000..beb78afca54
--- /dev/null
+++ b/messagebus/src/tests/routable/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+routable_test
+messagebus_routable_test_app
diff --git a/messagebus/src/tests/routable/CMakeLists.txt b/messagebus/src/tests/routable/CMakeLists.txt
new file mode 100644
index 00000000000..d44eca1dc71
--- /dev/null
+++ b/messagebus/src/tests/routable/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_routable_test_app
+ SOURCES
+ routable.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_routable_test_app COMMAND messagebus_routable_test_app)
diff --git a/messagebus/src/tests/routable/DESC b/messagebus/src/tests/routable/DESC
new file mode 100644
index 00000000000..2aa64df8271
--- /dev/null
+++ b/messagebus/src/tests/routable/DESC
@@ -0,0 +1 @@
+routable test. Take a look at routable.cpp for details.
diff --git a/messagebus/src/tests/routable/FILES b/messagebus/src/tests/routable/FILES
new file mode 100644
index 00000000000..bacc8b159f8
--- /dev/null
+++ b/messagebus/src/tests/routable/FILES
@@ -0,0 +1 @@
+routable.cpp
diff --git a/messagebus/src/tests/routable/routable.cpp b/messagebus/src/tests/routable/routable.cpp
new file mode 100644
index 00000000000..adde39dc51c
--- /dev/null
+++ b/messagebus/src/tests/routable/routable.cpp
@@ -0,0 +1,94 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routable_test");
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/message.h>
+#include <vespa/messagebus/reply.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("routable_test");
+
+ {
+ // Test message swap state.
+ SimpleMessage foo("foo");
+ Route fooRoute = Route::parse("foo");
+ foo.setRoute(fooRoute);
+ foo.setRetry(1);
+ foo.setTimeReceivedNow();
+ foo.setTimeRemaining(2);
+
+ SimpleMessage bar("bar");
+ Route barRoute = Route::parse("bar");
+ bar.setRoute(barRoute);
+ bar.setRetry(3);
+ bar.setTimeReceivedNow();
+ bar.setTimeRemaining(4);
+
+ foo.swapState(bar);
+ EXPECT_EQUAL(barRoute.toString(), foo.getRoute().toString());
+ EXPECT_EQUAL(fooRoute.toString(), bar.getRoute().toString());
+ EXPECT_EQUAL(3u, foo.getRetry());
+ EXPECT_EQUAL(1u, bar.getRetry());
+ EXPECT_TRUE(foo.getTimeReceived() >= bar.getTimeReceived());
+ EXPECT_EQUAL(4u, foo.getTimeRemaining());
+ EXPECT_EQUAL(2u, bar.getTimeRemaining());
+ }
+ {
+ // Test reply swap state.
+ SimpleReply foo("foo");
+ foo.setMessage(Message::UP(new SimpleMessage("foo")));
+ foo.setRetryDelay(1);
+ foo.addError(Error(ErrorCode::APP_FATAL_ERROR, "fatal"));
+ foo.addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "transient"));
+
+ SimpleReply bar("bar");
+ bar.setMessage(Message::UP(new SimpleMessage("bar")));
+ bar.setRetryDelay(2);
+ bar.addError(Error(ErrorCode::ERROR_LIMIT, "err"));
+
+ foo.swapState(bar);
+ EXPECT_EQUAL("bar", static_cast<SimpleMessage&>(*foo.getMessage()).getValue());
+ EXPECT_EQUAL("foo", static_cast<SimpleMessage&>(*bar.getMessage()).getValue());
+ EXPECT_EQUAL(2.0, foo.getRetryDelay());
+ EXPECT_EQUAL(1.0, bar.getRetryDelay());
+ EXPECT_EQUAL(1u, foo.getNumErrors());
+ EXPECT_EQUAL(2u, bar.getNumErrors());
+ }
+ {
+ // Test message discard logic.
+ Receptor handler;
+ SimpleMessage msg("foo");
+ msg.pushHandler(handler);
+ msg.discard();
+
+ Reply::UP reply = handler.getReply(0);
+ ASSERT_TRUE(reply.get() == NULL);
+ }
+ {
+ // Test reply discard logic.
+ Receptor handler;
+ SimpleMessage msg("foo");
+ msg.pushHandler(handler);
+
+ SimpleReply reply("bar");
+ reply.swapState(msg);
+ reply.discard();
+
+ Reply::UP ap = handler.getReply(0);
+ ASSERT_TRUE(ap.get() == NULL);
+ }
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/routablequeue/.gitignore b/messagebus/src/tests/routablequeue/.gitignore
new file mode 100644
index 00000000000..19c961345c9
--- /dev/null
+++ b/messagebus/src/tests/routablequeue/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+routablequeue_test
+messagebus_routablequeue_test_app
diff --git a/messagebus/src/tests/routablequeue/CMakeLists.txt b/messagebus/src/tests/routablequeue/CMakeLists.txt
new file mode 100644
index 00000000000..18e2be0a748
--- /dev/null
+++ b/messagebus/src/tests/routablequeue/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_routablequeue_test_app
+ SOURCES
+ routablequeue.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_routablequeue_test_app COMMAND messagebus_routablequeue_test_app)
diff --git a/messagebus/src/tests/routablequeue/DESC b/messagebus/src/tests/routablequeue/DESC
new file mode 100644
index 00000000000..b1613b4b2f2
--- /dev/null
+++ b/messagebus/src/tests/routablequeue/DESC
@@ -0,0 +1 @@
+routablequeue test. Take a look at routablequeue.cpp for details.
diff --git a/messagebus/src/tests/routablequeue/FILES b/messagebus/src/tests/routablequeue/FILES
new file mode 100644
index 00000000000..44a342e00a3
--- /dev/null
+++ b/messagebus/src/tests/routablequeue/FILES
@@ -0,0 +1 @@
+routablequeue.cpp
diff --git a/messagebus/src/tests/routablequeue/routablequeue.cpp b/messagebus/src/tests/routablequeue/routablequeue.cpp
new file mode 100644
index 00000000000..09d14b03983
--- /dev/null
+++ b/messagebus/src/tests/routablequeue/routablequeue.cpp
@@ -0,0 +1,110 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routablequeue_test");
+
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+class TestMessage : public SimpleMessage {
+private:
+ uint32_t _id;
+ static uint32_t _cnt;
+public:
+ TestMessage(uint32_t id) : SimpleMessage(""), _id(id) { ++_cnt; }
+ virtual ~TestMessage() { --_cnt; }
+ virtual uint32_t getType() const { return _id; }
+ static uint32_t getCnt() { return _cnt; }
+};
+uint32_t TestMessage::_cnt = 0;
+
+class TestReply : public SimpleReply {
+private:
+ uint32_t _id;
+ static uint32_t _cnt;
+public:
+ TestReply(uint32_t id) : SimpleReply(""), _id(id) { ++_cnt; }
+ virtual ~TestReply() { --_cnt; }
+ virtual uint32_t getType() const { return _id; }
+ static uint32_t getCnt() { return _cnt; }
+};
+uint32_t TestReply::_cnt = 0;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("routablequeue_test");
+ {
+ RoutableQueue rq;
+ EXPECT_TRUE(rq.size() == 0);
+ EXPECT_TRUE(rq.dequeue(0).get() == 0);
+ EXPECT_TRUE(rq.dequeue(100).get() == 0);
+ EXPECT_TRUE(TestMessage::getCnt() == 0);
+ EXPECT_TRUE(TestReply::getCnt() == 0);
+ rq.enqueue(Routable::UP(new TestMessage(101)));
+ EXPECT_TRUE(rq.size() == 1);
+ EXPECT_TRUE(TestMessage::getCnt() == 1);
+ EXPECT_TRUE(TestReply::getCnt() == 0);
+ rq.enqueue(Routable::UP(new TestReply(201)));
+ EXPECT_TRUE(rq.size() == 2);
+ EXPECT_TRUE(TestMessage::getCnt() == 1);
+ EXPECT_TRUE(TestReply::getCnt() == 1);
+ rq.handleMessage(Message::UP(new TestMessage(102)));
+ EXPECT_TRUE(rq.size() == 3);
+ EXPECT_TRUE(TestMessage::getCnt() == 2);
+ EXPECT_TRUE(TestReply::getCnt() == 1);
+ rq.handleReply(Reply::UP(new TestReply(202)));
+ EXPECT_TRUE(rq.size() == 4);
+ EXPECT_TRUE(TestMessage::getCnt() == 2);
+ EXPECT_TRUE(TestReply::getCnt() == 2);
+ {
+ Routable::UP r = rq.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ EXPECT_TRUE(rq.size() == 3);
+ EXPECT_TRUE(r->getType() == 101);
+ }
+ EXPECT_TRUE(TestMessage::getCnt() == 1);
+ EXPECT_TRUE(TestReply::getCnt() == 2);
+ {
+ Routable::UP r = rq.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ EXPECT_TRUE(rq.size() == 2);
+ EXPECT_TRUE(r->getType() == 201);
+ }
+ EXPECT_TRUE(TestMessage::getCnt() == 1);
+ EXPECT_TRUE(TestReply::getCnt() == 1);
+ rq.handleMessage(Message::UP(new TestMessage(103)));
+ EXPECT_TRUE(rq.size() == 3);
+ EXPECT_TRUE(TestMessage::getCnt() == 2);
+ EXPECT_TRUE(TestReply::getCnt() == 1);
+ rq.handleReply(Reply::UP(new TestReply(203)));
+ EXPECT_TRUE(rq.size() == 4);
+ EXPECT_TRUE(TestMessage::getCnt() == 2);
+ EXPECT_TRUE(TestReply::getCnt() == 2);
+ {
+ Routable::UP r = rq.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ EXPECT_TRUE(rq.size() == 3);
+ EXPECT_TRUE(r->getType() == 102);
+ }
+ EXPECT_TRUE(TestMessage::getCnt() == 1);
+ EXPECT_TRUE(TestReply::getCnt() == 2);
+ {
+ Routable::UP r = rq.dequeue(0);
+ ASSERT_TRUE(r.get() != 0);
+ EXPECT_TRUE(rq.size() == 2);
+ EXPECT_TRUE(r->getType() == 202);
+ }
+ EXPECT_TRUE(TestMessage::getCnt() == 1);
+ EXPECT_TRUE(TestReply::getCnt() == 1);
+ }
+ EXPECT_TRUE(TestMessage::getCnt() == 0);
+ EXPECT_TRUE(TestReply::getCnt() == 0);
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/routeparser/.gitignore b/messagebus/src/tests/routeparser/.gitignore
new file mode 100644
index 00000000000..8df44f3b268
--- /dev/null
+++ b/messagebus/src/tests/routeparser/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+routeparser_test
+messagebus_routeparser_test_app
diff --git a/messagebus/src/tests/routeparser/CMakeLists.txt b/messagebus/src/tests/routeparser/CMakeLists.txt
new file mode 100644
index 00000000000..712672c6b20
--- /dev/null
+++ b/messagebus/src/tests/routeparser/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_routeparser_test_app
+ SOURCES
+ routeparser.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_routeparser_test_app COMMAND messagebus_routeparser_test_app)
diff --git a/messagebus/src/tests/routeparser/DESC b/messagebus/src/tests/routeparser/DESC
new file mode 100644
index 00000000000..4f38ec8b2bd
--- /dev/null
+++ b/messagebus/src/tests/routeparser/DESC
@@ -0,0 +1 @@
+routeparser test. Take a look at routeparser.cpp for details.
diff --git a/messagebus/src/tests/routeparser/FILES b/messagebus/src/tests/routeparser/FILES
new file mode 100644
index 00000000000..3a562440161
--- /dev/null
+++ b/messagebus/src/tests/routeparser/FILES
@@ -0,0 +1 @@
+routeparser.cpp
diff --git a/messagebus/src/tests/routeparser/routeparser.cpp b/messagebus/src/tests/routeparser/routeparser.cpp
new file mode 100644
index 00000000000..d5522a7167a
--- /dev/null
+++ b/messagebus/src/tests/routeparser/routeparser.cpp
@@ -0,0 +1,280 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routeparser_test");
+
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/routing/errordirective.h>
+#include <vespa/messagebus/routing/policydirective.h>
+#include <vespa/messagebus/routing/route.h>
+#include <vespa/messagebus/routing/routedirective.h>
+#include <vespa/messagebus/routing/tcpdirective.h>
+#include <vespa/messagebus/routing/verbatimdirective.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+class Test : public vespalib::TestApp {
+public:
+ int Main();
+ void testHopParser();
+ void testHopParserErrors();
+ void testRouteParser();
+ void testRouteParserErrors();
+
+private:
+ bool testError(const Route &route, const string &msg);
+ bool testError(const Hop &hop, const string &msg);
+ bool testErrorDirective(IHopDirective::SP dir, const string &msg);
+ bool testPolicyDirective(IHopDirective::SP dir, const string &name, const string &param);
+ bool testRouteDirective(IHopDirective::SP dir, const string &name);
+ bool testTcpDirective(IHopDirective::SP dir, const string &host, uint32_t port, const string &session);
+ bool testVerbatimDirective(IHopDirective::SP dir, const string &image);
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("routeparser_test");
+
+ testHopParser(); TEST_FLUSH();
+ testHopParserErrors(); TEST_FLUSH();
+ testRouteParser(); TEST_FLUSH();
+ testRouteParserErrors(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+bool
+Test::testError(const Route &route, const string &msg)
+{
+ if (!EXPECT_EQUAL(1u, route.getNumHops())) {
+ return false;
+ }
+ if (!testError(route.getHop(0), msg)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+Test::testError(const Hop &hop, const string &msg)
+{
+ LOG(info, "%s", hop.toDebugString().c_str());
+ if (!EXPECT_EQUAL(1u, hop.getNumDirectives())) {
+ return false;
+ }
+ if (!testErrorDirective(hop.getDirective(0), msg)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+Test::testErrorDirective(IHopDirective::SP dir, const string &msg)
+{
+ if (!EXPECT_TRUE(dir.get() != NULL)) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(IHopDirective::TYPE_ERROR, dir->getType())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(msg, static_cast<const ErrorDirective&>(*dir).getMessage())) {
+ return false;
+ }
+ return true;
+}
+
+bool
+Test::testPolicyDirective(IHopDirective::SP dir, const string &name, const string &param)
+{
+ if (!EXPECT_TRUE(dir.get() != NULL)) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(IHopDirective::TYPE_POLICY, dir->getType())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(name, static_cast<const PolicyDirective&>(*dir).getName())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(param, static_cast<const PolicyDirective&>(*dir).getParam())) {
+ return false;
+ }
+ return true;
+}
+
+bool
+Test::testRouteDirective(IHopDirective::SP dir, const string &name)
+{
+ if (!EXPECT_TRUE(dir.get() != NULL)) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(IHopDirective::TYPE_ROUTE, dir->getType())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(name, static_cast<const RouteDirective&>(*dir).getName())) {
+ return false;
+ }
+ return true;
+}
+
+bool
+Test::testTcpDirective(IHopDirective::SP dir, const string &host, uint32_t port, const string &session)
+{
+ if (!EXPECT_TRUE(dir.get() != NULL)) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(IHopDirective::TYPE_TCP, dir->getType())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(host, static_cast<const TcpDirective&>(*dir).getHost())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(port, static_cast<const TcpDirective&>(*dir).getPort())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(session, static_cast<const TcpDirective&>(*dir).getSession())) {
+ return false;
+ }
+ return true;
+}
+
+bool
+Test::testVerbatimDirective(IHopDirective::SP dir, const string &image)
+{
+ if (!EXPECT_TRUE(dir.get() != NULL)) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(IHopDirective::TYPE_VERBATIM, dir->getType())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(image, static_cast<const VerbatimDirective&>(*dir).getImage())) {
+ return false;
+ }
+ return true;
+}
+
+void
+Test::testHopParser()
+{
+ {
+ Hop hop = Hop::parse("foo");
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testVerbatimDirective(hop.getDirective(0), "foo"));
+ }
+ {
+ Hop hop = Hop::parse("foo/bar");
+ EXPECT_EQUAL(2u, hop.getNumDirectives());
+ EXPECT_TRUE(testVerbatimDirective(hop.getDirective(0), "foo"));
+ EXPECT_TRUE(testVerbatimDirective(hop.getDirective(1), "bar"));
+ }
+ {
+ Hop hop = Hop::parse("tcp/foo:666/bar");
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testTcpDirective(hop.getDirective(0), "foo", 666, "bar"));
+ }
+ {
+ Hop hop = Hop::parse("route:foo");
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testRouteDirective(hop.getDirective(0), "foo"));
+ }
+ {
+ Hop hop = Hop::parse("[Extern:tcp/localhost:3619;foo/bar]");
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testPolicyDirective(hop.getDirective(0), "Extern", "tcp/localhost:3619;foo/bar"));
+ }
+ {
+ Hop hop = Hop::parse("[AND:foo bar]");
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testPolicyDirective(hop.getDirective(0), "AND", "foo bar"));
+ }
+ {
+ Hop hop = Hop::parse("[DocumentRouteSelector:raw:route[2]\n"
+ "route[0].name \"foo\"\n"
+ "route[0].selector \"testdoc\"\n"
+ "route[0].feed \"myfeed\"\n"
+ "route[1].name \"bar\"\n"
+ "route[1].selector \"other\"\n"
+ "route[1].feed \"myfeed\"\n"
+ "]");
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testPolicyDirective(hop.getDirective(0), "DocumentRouteSelector",
+ "raw:route[2]\n"
+ "route[0].name \"foo\"\n"
+ "route[0].selector \"testdoc\"\n"
+ "route[0].feed \"myfeed\"\n"
+ "route[1].name \"bar\"\n"
+ "route[1].selector \"other\"\n"
+ "route[1].feed \"myfeed\"\n"));
+ }
+ {
+ Hop hop = Hop::parse("[DocumentRouteSelector:raw:route[1]\n"
+ "route[0].name \"docproc/cluster.foo\"\n"
+ "route[0].selector \"testdoc\"\n"
+ "route[0].feed \"myfeed\"\n"
+ "]");
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testPolicyDirective(hop.getDirective(0), "DocumentRouteSelector",
+ "raw:route[1]\n"
+ "route[0].name \"docproc/cluster.foo\"\n"
+ "route[0].selector \"testdoc\"\n"
+ "route[0].feed \"myfeed\"\n"));
+ }
+}
+
+void
+Test::testHopParserErrors()
+{
+ EXPECT_TRUE(testError(Hop::parse(""), "Failed to parse empty string."));
+ EXPECT_TRUE(testError(Hop::parse("[foo"), "Unexpected token '': syntax error"));
+ EXPECT_TRUE(testError(Hop::parse("foo/[bar]]"), "Unexpected token ']': syntax error"));
+ EXPECT_TRUE(testError(Hop::parse("foo bar"), "Failed to completely parse 'foo bar'."));
+}
+
+void
+Test::testRouteParser()
+{
+ {
+ Route route = Route::parse("foo bar/baz");
+ EXPECT_EQUAL(2u, route.getNumHops());
+ {
+ const Hop &hop = route.getHop(0);
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testVerbatimDirective(hop.getDirective(0), "foo"));
+ }
+ {
+ const Hop &hop = route.getHop(1);
+ EXPECT_EQUAL(2u, hop.getNumDirectives());
+ EXPECT_TRUE(testVerbatimDirective(hop.getDirective(0), "bar"));
+ EXPECT_TRUE(testVerbatimDirective(hop.getDirective(1), "baz"));
+ }
+ }
+ {
+ Route route = Route::parse("[Extern:tcp/localhost:3633;itr/session] default");
+ EXPECT_EQUAL(2u, route.getNumHops());
+ {
+ const Hop &hop = route.getHop(0);
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testPolicyDirective(hop.getDirective(0), "Extern", "tcp/localhost:3633;itr/session"));
+ }
+ {
+ const Hop &hop = route.getHop(1);
+ EXPECT_EQUAL(1u, hop.getNumDirectives());
+ EXPECT_TRUE(testVerbatimDirective(hop.getDirective(0), "default"));
+ }
+ }
+}
+
+void
+Test::testRouteParserErrors()
+{
+ EXPECT_TRUE(testError(Route::parse(""), "Failed to parse empty string."));
+ EXPECT_TRUE(testError(Route::parse("foo [bar"), "Unexpected token '': syntax error"));
+ EXPECT_TRUE(testError(Route::parse("foo bar/[baz]]"), "Unexpected token ']': syntax error"));
+}
diff --git a/messagebus/src/tests/routing/.gitignore b/messagebus/src/tests/routing/.gitignore
new file mode 100644
index 00000000000..9a8bd74d953
--- /dev/null
+++ b/messagebus/src/tests/routing/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+routing_test
+messagebus_routing_test_app
diff --git a/messagebus/src/tests/routing/CMakeLists.txt b/messagebus/src/tests/routing/CMakeLists.txt
new file mode 100644
index 00000000000..c2b38e4c4b4
--- /dev/null
+++ b/messagebus/src/tests/routing/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_routing_test_app
+ SOURCES
+ routing.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_routing_test_app COMMAND messagebus_routing_test_app)
diff --git a/messagebus/src/tests/routing/DESC b/messagebus/src/tests/routing/DESC
new file mode 100644
index 00000000000..bb46d61d6cc
--- /dev/null
+++ b/messagebus/src/tests/routing/DESC
@@ -0,0 +1 @@
+routing test. Take a look at routing.cpp for details.
diff --git a/messagebus/src/tests/routing/FILES b/messagebus/src/tests/routing/FILES
new file mode 100644
index 00000000000..fec1f48186a
--- /dev/null
+++ b/messagebus/src/tests/routing/FILES
@@ -0,0 +1 @@
+routing.cpp
diff --git a/messagebus/src/tests/routing/routing.cpp b/messagebus/src/tests/routing/routing.cpp
new file mode 100644
index 00000000000..b9c673263b5
--- /dev/null
+++ b/messagebus/src/tests/routing/routing.cpp
@@ -0,0 +1,1580 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routing_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routing/errordirective.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/messagebus/testlib/custompolicy.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/messagebus/vtag.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class StringList : public std::vector<string> {
+public:
+ StringList &add(const string &str);
+};
+
+StringList &
+StringList::add(const string &str)
+{
+ std::vector<string>::push_back(str);
+ return *this;
+}
+
+class UIntList : public std::vector<uint32_t> {
+public:
+ UIntList &add(uint32_t i);
+};
+
+UIntList &
+UIntList::add(uint32_t i)
+{
+ std::vector<uint32_t>::push_back(i);
+ return *this;
+}
+
+class RemoveReplyPolicy : public CustomPolicy {
+private:
+ uint32_t _idxRemove;
+public:
+ RemoveReplyPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> consumableErrors,
+ const std::vector<Route> routes,
+ uint32_t idxRemove);
+ void merge(RoutingContext &ctx);
+};
+
+RemoveReplyPolicy::RemoveReplyPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> consumableErrors,
+ const std::vector<Route> routes,
+ uint32_t idxRemove) :
+ CustomPolicy::CustomPolicy(selectOnRetry, consumableErrors, routes),
+ _idxRemove(idxRemove)
+{
+ // empty
+}
+
+void
+RemoveReplyPolicy::merge(RoutingContext &ctx)
+{
+ ctx.setReply(ctx.getChildIterator().skip(_idxRemove).removeReply());
+}
+
+class RemoveReplyPolicyFactory : public SimpleProtocol::IPolicyFactory {
+private:
+ bool _selectOnRetry;
+ std::vector<uint32_t> _consumableErrors;
+ uint32_t _idxRemove;
+public:
+ RemoveReplyPolicyFactory(bool selectOnRetry,
+ const std::vector<uint32_t> &consumableErrors,
+ uint32_t idxRemove);
+ IRoutingPolicy::UP create(const string &param);
+};
+
+RemoveReplyPolicyFactory::RemoveReplyPolicyFactory(bool selectOnRetry,
+ const std::vector<uint32_t> &consumableErrors,
+ uint32_t idxRemove) :
+ _selectOnRetry(selectOnRetry),
+ _consumableErrors(consumableErrors),
+ _idxRemove(idxRemove)
+{
+ // empty
+}
+
+IRoutingPolicy::UP
+RemoveReplyPolicyFactory::create(const string &param)
+{
+ std::vector<Route> routes;
+ CustomPolicyFactory::parseRoutes(param, routes);
+ return IRoutingPolicy::UP(new RemoveReplyPolicy(_selectOnRetry, _consumableErrors, routes, _idxRemove));
+}
+
+class ReuseReplyPolicy : public CustomPolicy {
+private:
+ std::vector<uint32_t> _errorMask;
+public:
+ ReuseReplyPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> &errorMask,
+ const std::vector<Route> &routes);
+ void merge(RoutingContext &ctx);
+};
+
+ReuseReplyPolicy::ReuseReplyPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> &errorMask,
+ const std::vector<Route> &routes) :
+ CustomPolicy::CustomPolicy(selectOnRetry, errorMask, routes),
+ _errorMask(errorMask)
+{
+ // empty
+}
+
+void
+ReuseReplyPolicy::merge(RoutingContext &ctx)
+{
+ Reply::UP ret(new EmptyReply());
+ uint32_t idx = 0;
+ int idxFirstOk = -1;
+ for (RoutingNodeIterator it = ctx.getChildIterator();
+ it.isValid(); it.next(), ++idx)
+ {
+ const Reply &ref = it.getReplyRef();
+ if (!ref.hasErrors()) {
+ if (idxFirstOk < 0) {
+ idxFirstOk = idx;
+ }
+ } else {
+ for (uint32_t i = 0; i < ref.getNumErrors(); ++i) {
+ Error err = ref.getError(i);
+ if (find(_errorMask.begin(), _errorMask.end(), err.getCode()) == _errorMask.end()) {
+ ret->addError(err);
+ }
+ }
+ }
+ }
+ if (ret->hasErrors()) {
+ ctx.setReply(std::move(ret));
+ } else {
+ ctx.setReply(ctx.getChildIterator().skip(idxFirstOk).removeReply());
+ }
+}
+
+class ReuseReplyPolicyFactory : public SimpleProtocol::IPolicyFactory {
+private:
+ bool _selectOnRetry;
+ std::vector<uint32_t> _errorMask;
+public:
+ ReuseReplyPolicyFactory(bool selectOnRetry,
+ const std::vector<uint32_t> &errorMask);
+ IRoutingPolicy::UP create(const string &param);
+};
+
+ReuseReplyPolicyFactory::ReuseReplyPolicyFactory(bool selectOnRetry,
+ const std::vector<uint32_t> &errorMask) :
+ _selectOnRetry(selectOnRetry),
+ _errorMask(errorMask)
+{
+ // empty
+}
+
+IRoutingPolicy::UP
+ReuseReplyPolicyFactory::create(const string &param)
+{
+ std::vector<Route> routes;
+ CustomPolicyFactory::parseRoutes(param, routes);
+ return IRoutingPolicy::UP(new ReuseReplyPolicy(_selectOnRetry, _errorMask, routes));
+}
+
+class SetReplyPolicy : public IRoutingPolicy {
+private:
+ bool _selectOnRetry;
+ std::vector<uint32_t> _errors;
+ string _param;
+ uint32_t _idx;
+public:
+ SetReplyPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> &errors,
+ const string &param);
+ void select(RoutingContext &ctx);
+ void merge(RoutingContext &ctx);
+};
+
+SetReplyPolicy::SetReplyPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> &errors,
+ const string &param) :
+ _selectOnRetry(selectOnRetry),
+ _errors(errors),
+ _param(param),
+ _idx(0)
+{
+ // empty
+}
+
+void
+SetReplyPolicy::select(RoutingContext &ctx)
+{
+ uint32_t idx = _idx++;
+ uint32_t err = _errors[idx < _errors.size() ? idx : _errors.size() - 1];
+ if (err != ErrorCode::NONE) {
+ ctx.setError(err, _param);
+ } else {
+ ctx.setReply(Reply::UP(new EmptyReply()));
+ }
+ ctx.setSelectOnRetry(_selectOnRetry);
+}
+
+void
+SetReplyPolicy::merge(RoutingContext &ctx)
+{
+ Reply::UP reply(new EmptyReply());
+ reply->addError(Error(ErrorCode::FATAL_ERROR, "Merge should not be called when select() sets a reply."));
+ ctx.setReply(std::move(reply));
+}
+
+class SetReplyPolicyFactory : public SimpleProtocol::IPolicyFactory {
+private:
+ bool _selectOnRetry;
+ std::vector<uint32_t> _errors;
+public:
+ SetReplyPolicyFactory(bool selectOnRetry,
+ const std::vector<uint32_t> &errors);
+ IRoutingPolicy::UP create(const string &param);
+};
+
+SetReplyPolicyFactory::SetReplyPolicyFactory(bool selectOnRetry,
+ const std::vector<uint32_t> &errors) :
+ _selectOnRetry(selectOnRetry),
+ _errors(errors)
+{
+ // empty
+}
+
+IRoutingPolicy::UP
+SetReplyPolicyFactory::create(const string &param)
+{
+ return IRoutingPolicy::UP(new SetReplyPolicy(_selectOnRetry, _errors, param));
+}
+
+class TestException : public std::exception {
+ virtual const char* what() const throw() {
+ return "{test exception}";
+ }
+};
+
+class SelectExceptionPolicy : public IRoutingPolicy {
+public:
+ void select(RoutingContext &ctx) {
+ (void)ctx;
+ throw TestException();
+ }
+
+ void merge(RoutingContext &ctx) {
+ (void)ctx;
+ }
+};
+
+class SelectExceptionPolicyFactory : public SimpleProtocol::IPolicyFactory {
+public:
+ IRoutingPolicy::UP create(const string &param) {
+ (void)param;
+ return IRoutingPolicy::UP(new SelectExceptionPolicy());
+ }
+};
+
+class MergeExceptionPolicy : public IRoutingPolicy {
+private:
+ const string _select;
+
+public:
+ MergeExceptionPolicy(const string &param)
+ : _select(param)
+ {
+ // empty
+ }
+
+ void select(RoutingContext &ctx) {
+ ctx.addChild(Route::parse(_select));
+ }
+
+ void merge(RoutingContext &ctx) {
+ (void)ctx;
+ throw TestException();
+ }
+};
+
+class MergeExceptionPolicyFactory : public SimpleProtocol::IPolicyFactory {
+public:
+ IRoutingPolicy::UP create(const string &param) {
+ return IRoutingPolicy::UP(new MergeExceptionPolicy(param));
+ }
+};
+
+class MyPolicyFactory : public SimpleProtocol::IPolicyFactory {
+private:
+ string _selectRoute;
+ uint32_t _selectError;
+ bool _selectException;
+ bool _mergeFromChild;
+ uint32_t _mergeError;
+ bool _mergeException;
+
+public:
+ friend class MyPolicy;
+
+ MyPolicyFactory(const string &selectRoute,
+ uint32_t &selectError,
+ bool selectException,
+ bool mergeFromChild,
+ uint32_t mergeError,
+ bool mergeException) :
+ _selectRoute(selectRoute),
+ _selectError(selectError),
+ _selectException(selectException),
+ _mergeFromChild(mergeFromChild),
+ _mergeError(mergeError),
+ _mergeException(mergeException)
+ {
+ // empty
+ }
+
+ IRoutingPolicy::UP
+ create(const string &param);
+
+ static MyPolicyFactory::SP
+ newInstance(const string &selectRoute,
+ uint32_t selectError,
+ bool selectException,
+ bool mergeFromChild,
+ uint32_t mergeError,
+ bool mergeException)
+ {
+ MyPolicyFactory::SP ptr;
+ ptr.reset(new MyPolicyFactory(selectRoute, selectError, selectException,
+ mergeFromChild, mergeError, mergeException));
+ return ptr;
+ }
+
+ static MyPolicyFactory::SP
+ newSelectAndMerge(const string &select)
+ {
+ return newInstance(select, ErrorCode::NONE, false, true, ErrorCode::NONE, false);
+ }
+
+ static MyPolicyFactory::SP
+ newEmptySelection()
+ {
+ return newInstance("", ErrorCode::NONE, false, false, ErrorCode::NONE, false);
+ }
+
+ static MyPolicyFactory::SP
+ newSelectError(uint32_t errCode)
+ {
+ return newInstance("", errCode, false, false, ErrorCode::NONE, false);
+ }
+
+ static MyPolicyFactory::SP
+ newSelectException()
+ {
+ return newInstance("", ErrorCode::NONE, true, false, ErrorCode::NONE, false);
+ }
+
+ static MyPolicyFactory::SP
+ newSelectAndThrow(const string &select)
+ {
+ return newInstance(select, ErrorCode::NONE, true, false, ErrorCode::NONE, false);
+ }
+
+ static MyPolicyFactory::SP
+ newEmptyMerge(const string &select)
+ {
+ return newInstance(select, ErrorCode::NONE, false, false, ErrorCode::NONE, false);
+ }
+
+ static MyPolicyFactory::SP
+ newMergeError(const string &select, int errCode)
+ {
+ return newInstance(select, ErrorCode::NONE, false, false, errCode, false);
+ }
+
+ static MyPolicyFactory::SP
+ newMergeException(const string &select)
+ {
+ return newInstance(select, ErrorCode::NONE, false, false, ErrorCode::NONE, true);
+ }
+
+ static MyPolicyFactory::SP
+ newMergeAndThrow(const string &select)
+ {
+ return newInstance(select, ErrorCode::NONE, false, true, ErrorCode::NONE, true);
+ }
+};
+
+class MyPolicy : public IRoutingPolicy {
+private:
+ const MyPolicyFactory &_parent;
+
+public:
+ MyPolicy(const MyPolicyFactory &parent) :
+ _parent(parent)
+ {
+ // empty
+ }
+
+ virtual void
+ select(RoutingContext &ctx)
+ {
+ if (!_parent._selectRoute.empty()) {
+ ctx.addChild(Route::parse(_parent._selectRoute));
+ }
+ if (_parent._selectError != ErrorCode::NONE) {
+ Reply::UP reply(new EmptyReply());
+ reply->addError(Error(_parent._selectError, "err"));
+ ctx.setReply(std::move(reply));
+ }
+ if (_parent._selectException) {
+ throw TestException();
+ }
+ }
+
+ virtual void
+ merge(RoutingContext &ctx)
+ {
+ if (_parent._mergeError != ErrorCode::NONE) {
+ Reply::UP reply(new EmptyReply());
+ reply->addError(Error(_parent._mergeError, "err"));
+ ctx.setReply(std::move(reply));
+ } else if (_parent._mergeFromChild) {
+ ctx.setReply(ctx.getChildIterator().removeReply());
+ }
+ if (_parent._mergeException) {
+ throw TestException();
+ }
+ }
+};
+
+IRoutingPolicy::UP
+MyPolicyFactory::create(const string &param)
+{
+ (void)param;
+ return IRoutingPolicy::UP(new MyPolicy(*this));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class TestData {
+public:
+ Slobrok _slobrok;
+ RetryTransientErrorsPolicy::SP _retryPolicy;
+ TestServer _srcServer;
+ SourceSession::UP _srcSession;
+ Receptor _srcHandler;
+ TestServer _dstServer;
+ DestinationSession::UP _dstSession;
+ Receptor _dstHandler;
+
+public:
+ TestData();
+ bool start();
+};
+
+class Test : public vespalib::TestApp {
+private:
+ Message::UP createMessage(const string &msg, uint32_t level = 9);
+ void setupRouting(TestData &data, const RoutingTableSpec &spec);
+ void setupPolicy(TestData &data, const string &policyName,
+ SimpleProtocol::IPolicyFactory::SP policy);
+ bool testAcknowledge(TestData &data);
+ bool testSend(TestData &data, const string &route, uint32_t level = 9);
+ bool testTrace(TestData &data, const std::vector<string> &expected);
+ bool testTrace(const std::vector<string> &expected, const Trace &trace);
+
+ static const double RECEPTOR_TIMEOUT;
+
+public:
+ int Main();
+ void testNoRoutingTable(TestData &data);
+ void testUnknownRoute(TestData &data);
+ void testNoRoute(TestData &data);
+ void testRecognizeHopName(TestData &data);
+ void testRecognizeRouteDirective(TestData &data);
+ void testRecognizeRouteName(TestData &data);
+ void testHopResolutionOverflow(TestData &data);
+ void testRouteResolutionOverflow(TestData &data);
+ void testInsertRoute(TestData &data);
+ void testErrorDirective(TestData &data);
+ void testSelectError(TestData &data);
+ void testSelectNone(TestData &data);
+ void testSelectOne(TestData &data);
+ void testResend1(TestData &data);
+ void testResend2(TestData &data);
+ void testNoResend(TestData &data);
+ void testSelectOnResend(TestData &data);
+ void testNoSelectOnResend(TestData &data);
+ void testCanConsumeError(TestData &data);
+ void testCantConsumeError(TestData &data);
+ void testNestedPolicies(TestData &data);
+ void testRemoveReply(TestData &data);
+ void testSetReply(TestData &data);
+ void testResendSetAndReuseReply(TestData &data);
+ void testResendSetAndRemoveReply(TestData &data);
+ void testHopIgnoresReply(TestData &data);
+ void testHopBlueprintIgnoresReply(TestData &data);
+ void testAcceptEmptyRoute(TestData &data);
+ void testAbortOnlyActiveNodes(TestData &data);
+ void testTimeout(TestData &data);
+ void testUnknownPolicy(TestData &data);
+ void testSelectException(TestData &data);
+ void testMergeException(TestData &data);
+
+ void requireThatIgnoreFlagPersistsThroughHopLookup(TestData &data);
+ void requireThatIgnoreFlagPersistsThroughRouteLookup(TestData &data);
+ void requireThatIgnoreFlagPersistsThroughPolicySelect(TestData &data);
+ void requireThatIgnoreFlagIsSerializedWithMessage(TestData &data);
+ void requireThatIgnoreFlagDoesNotInterfere(TestData &data);
+ void requireThatEmptySelectionCanBeIgnored(TestData &data);
+ void requireThatSelectErrorCanBeIgnored(TestData &data);
+ void requireThatSelectExceptionCanBeIgnored(TestData &data);
+ void requireThatSelectAndThrowCanBeIgnored(TestData &data);
+ void requireThatEmptyMergeCanBeIgnored(TestData &data);
+ void requireThatMergeErrorCanBeIgnored(TestData &data);
+ void requireThatMergeExceptionCanBeIgnored(TestData &data);
+ void requireThatMergeAndThrowCanBeIgnored(TestData &data);
+ void requireThatAllocServiceCanBeIgnored(TestData &data);
+ void requireThatDepthLimitCanBeIgnored(TestData &data);
+};
+
+const double Test::RECEPTOR_TIMEOUT = 120.0;
+
+TEST_APPHOOK(Test);
+
+TestData::TestData() :
+ _slobrok(),
+ _retryPolicy(new RetryTransientErrorsPolicy()),
+ _srcServer(MessageBusParams()
+ .setRetryPolicy(_retryPolicy)
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setSlobrokConfig(_slobrok.config())),
+ _srcSession(),
+ _srcHandler(),
+ _dstServer(MessageBusParams()
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("dst"))
+ .setSlobrokConfig(_slobrok.config())),
+ _dstSession(),
+ _dstHandler()
+{
+ _retryPolicy->setBaseDelay(0);
+}
+
+bool
+TestData::start()
+{
+ _srcSession = _srcServer.mb.createSourceSession(SourceSessionParams()
+ .setThrottlePolicy(IThrottlePolicy::SP())
+ .setReplyHandler(_srcHandler));
+ if (_srcSession.get() == NULL) {
+ return false;
+ }
+ _dstSession = _dstServer.mb.createDestinationSession(DestinationSessionParams()
+ .setName("session")
+ .setMessageHandler(_dstHandler));
+ if (_dstSession.get() == NULL) {
+ return false;
+ }
+ if (!_srcServer.waitSlobrok("dst/session", 1u)) {
+ return false;
+ }
+ return true;
+}
+
+Message::UP
+Test::createMessage(const string &msg, uint32_t level)
+{
+ Message::UP ret(new SimpleMessage(msg));
+ ret->getTrace().setLevel(level);
+ return ret;
+}
+
+void
+Test::setupRouting(TestData &data, const RoutingTableSpec &spec)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(spec));
+}
+
+void
+Test::setupPolicy(TestData &data, const string &policyName,
+ SimpleProtocol::IPolicyFactory::SP policy)
+{
+ IProtocol::SP ptr(new SimpleProtocol());
+ static_cast<SimpleProtocol&>(*ptr).addPolicyFactory(policyName, policy);
+ data._srcServer.mb.putProtocol(ptr);
+}
+
+bool
+Test::testAcknowledge(TestData &data)
+{
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ if (!EXPECT_TRUE(msg.get() != NULL)) {
+ return false;
+ }
+ data._dstSession->acknowledge(std::move(msg));
+ return true;
+}
+
+bool
+Test::testSend(TestData &data, const string &route, uint32_t level)
+{
+ Message::UP msg = createMessage("msg", level);
+ msg->setRoute(Route::parse(route));
+ return data._srcSession->send(std::move(msg)).isAccepted();
+}
+
+bool
+Test::testTrace(TestData &data, const std::vector<string> &expected)
+{
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ if (!EXPECT_TRUE(reply.get() != NULL)) {
+ return false;
+ }
+ printf("%s", reply->getTrace().toString().c_str());
+ if (!EXPECT_TRUE(!reply->hasErrors())) {
+ return false;
+ }
+ return testTrace(expected, reply->getTrace());
+}
+
+bool
+Test::testTrace(const std::vector<string> &expected, const Trace &trace)
+{
+ string version = Vtag::currentVersion.toString();
+ string actual = trace.toString();
+ size_t pos = 0;
+ for (uint32_t i = 0; i < expected.size(); ++i) {
+ string line = expected[i];
+ size_t versionIdx = line.find("${VERSION}");
+ if (versionIdx != string::npos) {
+ line = line.replace(versionIdx, 10, version);
+ }
+ if (line[0] == '-') {
+ string str = line.substr(1);
+ if (!EXPECT_TRUE(actual.find(str, pos) == string::npos)) {
+ LOG(error, "Line %d '%s' not expected.", i, str.c_str());
+ return false;
+ }
+ } else {
+ pos = actual.find(line, pos);
+ if (!EXPECT_TRUE(pos != string::npos)) {
+ LOG(error, "Line %d '%s' missing.", i, line.c_str());
+ return false;
+ }
+ ++pos;
+ }
+ }
+ return true;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("routing_test");
+
+ TestData data;
+ ASSERT_TRUE(data.start());
+
+ testNoRoutingTable(data); TEST_FLUSH();
+ testUnknownRoute(data); TEST_FLUSH();
+ testNoRoute(data); TEST_FLUSH();
+ testRecognizeHopName(data); TEST_FLUSH();
+ testRecognizeRouteDirective(data); TEST_FLUSH();
+ testRecognizeRouteName(data); TEST_FLUSH();
+ testHopResolutionOverflow(data); TEST_FLUSH();
+ testRouteResolutionOverflow(data); TEST_FLUSH();
+ testInsertRoute(data); TEST_FLUSH();
+ testErrorDirective(data); TEST_FLUSH();
+ testSelectError(data); TEST_FLUSH();
+ testSelectNone(data); TEST_FLUSH();
+ testSelectOne(data); TEST_FLUSH();
+ testResend1(data); TEST_FLUSH();
+ testResend2(data); TEST_FLUSH();
+ testNoResend(data); TEST_FLUSH();
+ testSelectOnResend(data); TEST_FLUSH();
+ testNoSelectOnResend(data); TEST_FLUSH();
+ testCanConsumeError(data); TEST_FLUSH();
+ testCantConsumeError(data); TEST_FLUSH();
+ testNestedPolicies(data); TEST_FLUSH();
+ testRemoveReply(data); TEST_FLUSH();
+ testSetReply(data); TEST_FLUSH();
+ testResendSetAndReuseReply(data); TEST_FLUSH();
+ testResendSetAndRemoveReply(data); TEST_FLUSH();
+ testHopIgnoresReply(data); TEST_FLUSH();
+ testHopBlueprintIgnoresReply(data); TEST_FLUSH();
+ testAcceptEmptyRoute(data); TEST_FLUSH();
+ testAbortOnlyActiveNodes(data); TEST_FLUSH();
+ testUnknownPolicy(data); TEST_FLUSH();
+ testSelectException(data); TEST_FLUSH();
+ testMergeException(data); TEST_FLUSH();
+
+ requireThatIgnoreFlagPersistsThroughHopLookup(data); TEST_FLUSH();
+ requireThatIgnoreFlagPersistsThroughRouteLookup(data); TEST_FLUSH();
+ requireThatIgnoreFlagPersistsThroughPolicySelect(data); TEST_FLUSH();
+ requireThatIgnoreFlagIsSerializedWithMessage(data); TEST_FLUSH();
+ requireThatIgnoreFlagDoesNotInterfere(data); TEST_FLUSH();
+ requireThatEmptySelectionCanBeIgnored(data); TEST_FLUSH();
+ requireThatSelectErrorCanBeIgnored(data); TEST_FLUSH();
+ requireThatSelectExceptionCanBeIgnored(data); TEST_FLUSH();
+ requireThatSelectAndThrowCanBeIgnored(data); TEST_FLUSH();
+ requireThatEmptyMergeCanBeIgnored(data); TEST_FLUSH();
+ requireThatMergeErrorCanBeIgnored(data); TEST_FLUSH();
+ requireThatMergeExceptionCanBeIgnored(data); TEST_FLUSH();
+ requireThatMergeAndThrowCanBeIgnored(data); TEST_FLUSH();
+ requireThatAllocServiceCanBeIgnored(data); TEST_FLUSH();
+ requireThatDepthLimitCanBeIgnored(data); TEST_FLUSH();
+
+ // This needs to be last because it changes timeouts:
+ testTimeout(data); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test::testNoRoutingTable(TestData &data)
+{
+ Result res = data._srcSession->send(createMessage("msg"), "foo");
+ EXPECT_TRUE(!res.isAccepted());
+ EXPECT_EQUAL((uint32_t)ErrorCode::ILLEGAL_ROUTE, res.getError().getCode());
+ printf("%s\n", res.getError().getMessage().c_str());
+ Message::UP msg = res.getMessage();
+ EXPECT_TRUE(msg.get() != NULL);
+}
+
+void
+Test::testUnknownRoute(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addHop(HopSpec("foo", "bar"))));
+ Result res = data._srcSession->send(createMessage("msg"), "baz");
+ EXPECT_TRUE(!res.isAccepted());
+ EXPECT_EQUAL((uint32_t)ErrorCode::ILLEGAL_ROUTE, res.getError().getCode());
+ printf("%s\n", res.getError().getMessage().c_str());
+ Message::UP msg = res.getMessage();
+ EXPECT_TRUE(msg.get() != NULL);
+}
+
+void
+Test::testNoRoute(TestData &data)
+{
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route()).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::ILLEGAL_ROUTE, reply->getError(0).getCode());
+}
+
+void
+Test::testRecognizeHopName(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addHop(HopSpec("dst", "dst/session"))));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+}
+
+void
+Test::testRecognizeRouteDirective(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("dst").addHop("dst/session"))
+ .addHop(HopSpec("dir", "route:dst"))));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dir")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+}
+
+void
+Test::testRecognizeRouteName(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("dst").addHop("dst/session"))));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+}
+
+void
+Test::testHopResolutionOverflow(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addHop(HopSpec("foo", "bar"))
+ .addHop(HopSpec("bar", "foo"))));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("foo")).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::ILLEGAL_ROUTE, reply->getError(0).getCode());
+}
+
+void
+Test::testRouteResolutionOverflow(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("foo").addHop("route:foo"))));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), "foo").isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::ILLEGAL_ROUTE, reply->getError(0).getCode());
+}
+
+void
+Test::testInsertRoute(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("foo").addHop("dst/session").addHop("bar"))));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("route:foo baz")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ EXPECT_EQUAL(2u, msg->getRoute().getNumHops());
+ EXPECT_EQUAL("bar", msg->getRoute().getHop(0).toString());
+ EXPECT_EQUAL("baz", msg->getRoute().getHop(1).toString());
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+}
+
+void
+Test::testErrorDirective(TestData &data)
+{
+ Route route = Route::parse("foo/bar/baz");
+ route.getHop(0).setDirective(1, IHopDirective::SP(new ErrorDirective("err")));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), route).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::ILLEGAL_ROUTE, reply->getError(0).getCode());
+ EXPECT_EQUAL("err", reply->getError(0).getMessage());
+}
+
+void
+Test::testSelectError(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom: ]")).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ LOG(info, "testSelectError trace=%s", reply->getTrace().toString().c_str());
+ LOG(info, "testSelectError error=%s", reply->getError(0).toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::ILLEGAL_ROUTE, reply->getError(0).getCode());
+}
+
+void
+Test::testSelectNone(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom]")).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::NO_SERVICES_FOR_ROUTE, reply->getError(0).getCode());
+}
+
+void
+Test::testSelectOne(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:dst/session]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+}
+
+void
+Test::testResend1(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "err1"));
+ data._dstSession->reply(std::move(reply));
+ msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "err2"));
+ data._dstSession->reply(std::move(reply));
+ msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("[APP_TRANSIENT_ERROR @ localhost]: err1")
+ .add("-[APP_TRANSIENT_ERROR @ localhost]: err1")
+ .add("[APP_TRANSIENT_ERROR @ localhost]: err2")
+ .add("-[APP_TRANSIENT_ERROR @ localhost]: err2"),
+ reply->getTrace()));
+}
+
+void
+Test::testResend2(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:dst/session]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "err1"));
+ data._dstSession->reply(std::move(reply));
+ msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "err2"));
+ data._dstSession->reply(std::move(reply));
+ msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Source session accepted a 3 byte message. 1 message(s) now pending.")
+ .add("Running routing policy 'Custom'.")
+ .add("Selecting { 'dst/session' }.")
+ .add("Component 'dst/session' selected by policy 'Custom'.")
+ .add("Resolving 'dst/session'.")
+ .add("Sending message (version ${VERSION}) from client to 'dst/session'")
+ .add("Message (type 1) received at 'dst' for session 'session'.")
+ .add("[APP_TRANSIENT_ERROR @ localhost]: err1")
+ .add("Sending reply (version ${VERSION}) from 'dst'.")
+ .add("Reply (type 0) received at client.")
+ .add("Routing policy 'Custom' merging replies.")
+ .add("Merged { 'dst/session' }.")
+ .add("Message scheduled for retry 1 in 0.00 seconds.")
+ .add("Resender resending message.")
+ .add("Running routing policy 'Custom'.")
+ .add("Selecting { 'dst/session' }.")
+ .add("Component 'dst/session' selected by policy 'Custom'.")
+ .add("Resolving 'dst/session'.")
+ .add("Sending message (version ${VERSION}) from client to 'dst/session'")
+ .add("Message (type 1) received at 'dst' for session 'session'.")
+ .add("[APP_TRANSIENT_ERROR @ localhost]: err2")
+ .add("Sending reply (version ${VERSION}) from 'dst'.")
+ .add("Reply (type 0) received at client.")
+ .add("Routing policy 'Custom' merging replies.")
+ .add("Merged { 'dst/session' }.")
+ .add("Message scheduled for retry 2 in 0.00 seconds.")
+ .add("Resender resending message.")
+ .add("Running routing policy 'Custom'.")
+ .add("Selecting { 'dst/session' }.")
+ .add("Component 'dst/session' selected by policy 'Custom'.")
+ .add("Resolving 'dst/session'.")
+ .add("Sending message (version ${VERSION}) from client to 'dst/session'")
+ .add("Message (type 1) received at 'dst' for session 'session'.")
+ .add("Sending reply (version ${VERSION}) from 'dst'.")
+ .add("Reply (type 0) received at client.")
+ .add("Routing policy 'Custom' merging replies.")
+ .add("Merged { 'dst/session' }.")
+ .add("Source session received reply. 0 message(s) now pending."),
+ reply->getTrace()));
+}
+
+void
+Test::testNoResend(TestData &data)
+{
+ data._retryPolicy->setEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "err1"));
+ data._dstSession->reply(std::move(reply));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::APP_TRANSIENT_ERROR, reply->getError(0).getCode());
+}
+
+void
+Test::testSelectOnResend(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:dst/session]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "err"));
+ data._dstSession->reply(std::move(reply));
+ msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Selecting { 'dst/session' }.")
+ .add("[APP_TRANSIENT_ERROR @ localhost]")
+ .add("-[APP_TRANSIENT_ERROR @ localhost]")
+ .add("Merged { 'dst/session' }.")
+ .add("Selecting { 'dst/session' }.")
+ .add("Sending reply")
+ .add("Merged { 'dst/session' }."),
+ reply->getTrace()));
+}
+
+void
+Test::testNoSelectOnResend(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(false)));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:dst/session]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "err"));
+ data._dstSession->reply(std::move(reply));
+ msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Selecting { 'dst/session' }.")
+ .add("[APP_TRANSIENT_ERROR @ localhost]")
+ .add("-[APP_TRANSIENT_ERROR @ localhost]")
+ .add("Merged { 'dst/session' }.")
+ .add("-Selecting { 'dst/session' }.")
+ .add("Sending reply")
+ .add("Merged { 'dst/session' }."),
+ reply->getTrace()));
+}
+
+void
+Test::testCanConsumeError(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(true, ErrorCode::NO_ADDRESS_FOR_SERVICE)));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:dst/session,dst/unknown]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::NO_ADDRESS_FOR_SERVICE, reply->getError(0).getCode());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Selecting { 'dst/session', 'dst/unknown' }.")
+ .add("[NO_ADDRESS_FOR_SERVICE @ localhost]")
+ .add("Sending reply")
+ .add("Merged { 'dst/session', 'dst/unknown' }."),
+ reply->getTrace()));
+}
+
+void
+Test::testCantConsumeError(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:dst/unknown]")).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::NO_ADDRESS_FOR_SERVICE, reply->getError(0).getCode());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Selecting { 'dst/unknown' }.")
+ .add("[NO_ADDRESS_FOR_SERVICE @ localhost]")
+ .add("Merged { 'dst/unknown' }."),
+ reply->getTrace()));
+}
+
+void
+Test::testNestedPolicies(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(true, ErrorCode::NO_ADDRESS_FOR_SERVICE)));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:[Custom:dst/session],[Custom:dst/unknown]]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::NO_ADDRESS_FOR_SERVICE, reply->getError(0).getCode());
+}
+
+void
+Test::testRemoveReply(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new RemoveReplyPolicyFactory(
+ true,
+ UIntList().add(ErrorCode::NO_ADDRESS_FOR_SERVICE),
+ 0)));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:[Custom:dst/session],[Custom:dst/unknown]]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("[NO_ADDRESS_FOR_SERVICE @ localhost]")
+ .add("-[NO_ADDRESS_FOR_SERVICE @ localhost]")
+ .add("Sending message")
+ .add("-Sending message"),
+ reply->getTrace()));
+}
+
+void
+Test::testSetReply(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Select", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(true, ErrorCode::APP_FATAL_ERROR)));
+ simple.addPolicyFactory("SetReply", SimpleProtocol::IPolicyFactory::SP(new SetReplyPolicyFactory(true, UIntList().add(ErrorCode::APP_FATAL_ERROR))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(false);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Select:[SetReply:foo],dst/session]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::APP_FATAL_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL("foo", reply->getError(0).getMessage());
+}
+
+void
+Test::testResendSetAndReuseReply(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("ReuseReply", SimpleProtocol::IPolicyFactory::SP(new ReuseReplyPolicyFactory(
+ false,
+ UIntList().add(ErrorCode::APP_FATAL_ERROR))));
+ simple.addPolicyFactory("SetReply", SimpleProtocol::IPolicyFactory::SP(new SetReplyPolicyFactory(
+ false,
+ UIntList().add(ErrorCode::APP_FATAL_ERROR))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[ReuseReply:[SetReply:foo],dst/session]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_TRANSIENT_ERROR, "dst"));
+ data._dstSession->reply(std::move(reply));
+ msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+}
+
+void
+Test::testResendSetAndRemoveReply(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("RemoveReply", SimpleProtocol::IPolicyFactory::SP(new RemoveReplyPolicyFactory(
+ false,
+ UIntList().add(ErrorCode::APP_TRANSIENT_ERROR),
+ 0)));
+ simple.addPolicyFactory("SetReply", SimpleProtocol::IPolicyFactory::SP(new SetReplyPolicyFactory(
+ false,
+ UIntList().add(ErrorCode::APP_TRANSIENT_ERROR).add(ErrorCode::APP_FATAL_ERROR))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[RemoveReply:[SetReply:foo],dst/session]")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::APP_FATAL_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL("foo", reply->getError(0).getMessage());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Resolving '[SetReply:foo]'.")
+ .add("Resolving 'dst/session'.")
+ .add("Resender resending message.")
+ .add("Resolving 'dst/session'.")
+ .add("Resolving '[SetReply:foo]'."),
+ reply->getTrace()));
+}
+
+void
+Test::testHopIgnoresReply(TestData &data)
+{
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("?dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR, "dst"));
+ data._dstSession->reply(std::move(reply));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Not waiting for a reply from 'dst/session'."),
+ reply->getTrace()));
+}
+
+void
+Test::testHopBlueprintIgnoresReply(TestData &data)
+{
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addHop(HopSpec("foo", "dst/session").setIgnoreResult(true))));
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("foo")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR, "dst"));
+ data._dstSession->reply(std::move(reply));
+ reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_TRUE(testTrace(StringList()
+ .add("Not waiting for a reply from 'dst/session'."),
+ reply->getTrace()));
+}
+
+void
+Test::testAcceptEmptyRoute(TestData &data)
+{
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/session")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ const Route &route = msg->getRoute();
+ EXPECT_EQUAL(0u, route.getNumHops());
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+}
+
+void
+Test::testAbortOnlyActiveNodes(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(false)));
+ simple.addPolicyFactory("SetReply", SimpleProtocol::IPolicyFactory::SP(new SetReplyPolicyFactory(
+ false,
+ UIntList()
+ .add(ErrorCode::APP_TRANSIENT_ERROR)
+ .add(ErrorCode::APP_TRANSIENT_ERROR)
+ .add(ErrorCode::APP_FATAL_ERROR))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._retryPolicy->setEnabled(true);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Custom:[SetReply:foo],?bar,dst/session]")).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(2u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::APP_FATAL_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL((uint32_t)ErrorCode::SEND_ABORTED, reply->getError(1).getCode());
+}
+
+void
+Test::testUnknownPolicy(TestData &data)
+{
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("[Unknown]")).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::UNKNOWN_POLICY, reply->getError(0).getCode());
+}
+
+void
+Test::testSelectException(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("SelectException",
+ SimpleProtocol::IPolicyFactory::SP(
+ new SelectExceptionPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"),
+ Route::parse("[SelectException]"))
+ .isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::POLICY_ERROR,
+ reply->getError(0).getCode());
+ EXPECT_EQUAL("Policy 'SelectException' threw an exception; {test exception}",
+ reply->getError(0).getMessage());
+}
+
+void
+Test::testMergeException(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("MergeException",
+ SimpleProtocol::IPolicyFactory::SP(
+ new MergeExceptionPolicyFactory()));
+ data._srcServer.mb.putProtocol(protocol);
+ Route route = Route::parse("[MergeException:dst/session]");
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), route)
+ .isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::POLICY_ERROR,
+ reply->getError(0).getCode());
+ EXPECT_EQUAL("Policy 'MergeException' threw an exception; {test exception}",
+ reply->getError(0).getMessage());
+}
+
+void
+Test::requireThatIgnoreFlagPersistsThroughHopLookup(TestData &data)
+{
+ setupRouting(data, RoutingTableSpec(SimpleProtocol::NAME).addHop(HopSpec("foo", "dst/unknown")));
+ ASSERT_TRUE(testSend(data, "?foo"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatIgnoreFlagPersistsThroughRouteLookup(TestData &data)
+{
+ setupRouting(data, RoutingTableSpec(SimpleProtocol::NAME).addRoute(RouteSpec("foo").addHop("dst/unknown")));
+ ASSERT_TRUE(testSend(data, "?foo"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatIgnoreFlagPersistsThroughPolicySelect(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newSelectAndMerge("dst/unknown"));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatIgnoreFlagIsSerializedWithMessage(TestData &data)
+{
+ ASSERT_TRUE(testSend(data, "dst/session foo ?bar"));
+ Message::UP msg = data._dstHandler.getMessage(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ Route route = msg->getRoute();
+ EXPECT_EQUAL(2u, route.getNumHops());
+ Hop hop = route.getHop(0);
+ EXPECT_EQUAL("foo", hop.toString());
+ EXPECT_TRUE(!hop.getIgnoreResult());
+ hop = route.getHop(1);
+ EXPECT_EQUAL("?bar", hop.toString());
+ EXPECT_TRUE(hop.getIgnoreResult());
+ data._dstSession->acknowledge(std::move(msg));
+ ASSERT_TRUE(testTrace(data, StringList().add("-Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatIgnoreFlagDoesNotInterfere(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newSelectAndMerge("dst/session"));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testTrace(data, StringList().add("-Ignoring errors in reply.")));
+ ASSERT_TRUE(testAcknowledge(data));
+}
+
+void
+Test::requireThatEmptySelectionCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newEmptySelection());
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatSelectErrorCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newSelectError(ErrorCode::APP_FATAL_ERROR));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatSelectExceptionCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newSelectException());
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatSelectAndThrowCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newSelectAndThrow("dst/session"));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatEmptyMergeCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newEmptyMerge("dst/session"));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testAcknowledge(data));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatMergeErrorCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newMergeError("dst/session", ErrorCode::APP_FATAL_ERROR));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testAcknowledge(data));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatMergeExceptionCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newMergeException("dst/session"));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testAcknowledge(data));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatMergeAndThrowCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newMergeAndThrow("dst/session"));
+ ASSERT_TRUE(testSend(data, "?[Custom]"));
+ ASSERT_TRUE(testAcknowledge(data));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatAllocServiceCanBeIgnored(TestData &data)
+{
+ ASSERT_TRUE(testSend(data, "?dst/unknown"));
+ ASSERT_TRUE(testTrace(data, StringList().add("Ignoring errors in reply.")));
+}
+
+void
+Test::requireThatDepthLimitCanBeIgnored(TestData &data)
+{
+ setupPolicy(data, "Custom", MyPolicyFactory::newSelectAndMerge("[Custom]"));
+ ASSERT_TRUE(testSend(data, "?[Custom]", 0));
+ ASSERT_TRUE(testTrace(data, StringList()));
+}
+
+void
+Test::testTimeout(TestData &data)
+{
+ data._retryPolicy->setEnabled(true);
+ data._retryPolicy->setBaseDelay(0.01);
+ data._srcSession->setTimeout(0.5);
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("dst/unknown")).isAccepted());
+ Reply::UP reply = data._srcHandler.getReply(RECEPTOR_TIMEOUT);
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(2u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::NO_ADDRESS_FOR_SERVICE, reply->getError(0).getCode());
+ EXPECT_EQUAL((uint32_t)ErrorCode::TIMEOUT, reply->getError(1).getCode());
+}
diff --git a/messagebus/src/tests/routingcontext/.gitignore b/messagebus/src/tests/routingcontext/.gitignore
new file mode 100644
index 00000000000..7c3133e4bca
--- /dev/null
+++ b/messagebus/src/tests/routingcontext/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+routingcontext_test
+messagebus_routingcontext_test_app
diff --git a/messagebus/src/tests/routingcontext/CMakeLists.txt b/messagebus/src/tests/routingcontext/CMakeLists.txt
new file mode 100644
index 00000000000..064487d2d71
--- /dev/null
+++ b/messagebus/src/tests/routingcontext/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_routingcontext_test_app
+ SOURCES
+ routingcontext.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_routingcontext_test_app COMMAND messagebus_routingcontext_test_app)
diff --git a/messagebus/src/tests/routingcontext/DESC b/messagebus/src/tests/routingcontext/DESC
new file mode 100644
index 00000000000..9e52d1d8055
--- /dev/null
+++ b/messagebus/src/tests/routingcontext/DESC
@@ -0,0 +1 @@
+routingcontext test. Take a look at routingcontext.cpp for details.
diff --git a/messagebus/src/tests/routingcontext/FILES b/messagebus/src/tests/routingcontext/FILES
new file mode 100644
index 00000000000..8eb1e780a73
--- /dev/null
+++ b/messagebus/src/tests/routingcontext/FILES
@@ -0,0 +1 @@
+routingcontext.cpp
diff --git a/messagebus/src/tests/routingcontext/routingcontext.cpp b/messagebus/src/tests/routingcontext/routingcontext.cpp
new file mode 100644
index 00000000000..02c7ef6dd72
--- /dev/null
+++ b/messagebus/src/tests/routingcontext/routingcontext.cpp
@@ -0,0 +1,389 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routingcontext_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+
+using namespace mbus;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+////////////////////////////////////////////////////////////////////////////////
+
+using vespalib::make_vespa_string;
+
+static const double TIMEOUT = 120;
+
+class StringList : public std::vector<string> {
+public:
+ StringList &add(const string &str);
+};
+
+StringList &
+StringList::add(const string &str)
+{
+ std::vector<string>::push_back(str); return *this;
+}
+
+class CustomPolicyFactory : public SimpleProtocol::IPolicyFactory {
+private:
+ friend class CustomPolicy;
+
+ bool _forward;
+ std::vector<string> _expectedAll;
+ std::vector<string> _expectedMatched;
+
+public:
+ CustomPolicyFactory(bool forward,
+ const std::vector<string> &all,
+ const std::vector<string> &matched);
+ IRoutingPolicy::UP create(const string &param);
+};
+
+class CustomPolicy : public IRoutingPolicy {
+private:
+ CustomPolicyFactory &_factory;
+
+public:
+ CustomPolicy(CustomPolicyFactory &factory);
+ void select(RoutingContext &ctx);
+ void merge(RoutingContext &ctx);
+};
+
+CustomPolicy::CustomPolicy(CustomPolicyFactory &factory) :
+ _factory(factory)
+{
+ // empty
+}
+
+void
+CustomPolicy::select(RoutingContext &ctx)
+{
+ Reply::UP reply(new EmptyReply());
+ reply->getTrace().setLevel(9);
+
+ const std::vector<Route> &all = ctx.getAllRecipients();
+ if (_factory._expectedAll.size() == all.size()) {
+ ctx.trace(1, make_vespa_string("Got %d expected recipients.", (uint32_t)all.size()));
+ for (std::vector<Route>::const_iterator it = all.begin();
+ it != all.end(); ++it)
+ {
+ if (find(_factory._expectedAll.begin(), _factory._expectedAll.end(), it->toString()) != _factory._expectedAll.end()) {
+ ctx.trace(1, make_vespa_string("Got expected recipient '%s'.", it->toString().c_str()));
+ } else {
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR,
+ make_vespa_string("Matched recipient '%s' not expected.",
+ it->toString().c_str())));
+ }
+ }
+ } else {
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR,
+ make_vespa_string("Expected %d recipients, got %d.",
+ (uint32_t)_factory._expectedAll.size(),
+ (uint32_t)all.size())));
+ }
+
+ if (ctx.getNumRecipients() == all.size()) {
+ for (uint32_t i = 0; i < all.size(); ++i) {
+ if (all[i].toString() == ctx.getRecipient(i).toString()) {
+ ctx.trace(1, make_vespa_string("getRecipient(%d) matches getAllRecipients()[%d]", i, i));
+ } else {
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR,
+ make_vespa_string("getRecipient(%d) differs from getAllRecipients()[%d]", i, i)));
+ }
+ }
+ } else {
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR,
+ "getNumRecipients() differs from getAllRecipients().size()"));
+ }
+
+ std::vector<Route> matched;
+ ctx.getMatchedRecipients(matched);
+ if (_factory._expectedMatched.size() == matched.size()) {
+ ctx.trace(1, make_vespa_string("Got %d expected recipients.", (uint32_t)matched.size()));
+ for (std::vector<Route>::iterator it = matched.begin();
+ it != matched.end(); ++it)
+ {
+ if (find(_factory._expectedMatched.begin(), _factory._expectedMatched.end(), it->toString()) != _factory._expectedMatched.end()) {
+ ctx.trace(1, make_vespa_string("Got matched recipient '%s'.", it->toString().c_str()));
+ } else {
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR,
+ make_vespa_string("Matched recipient '%s' not expected.",
+ it->toString().c_str())));
+ }
+ }
+ } else {
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR,
+ make_vespa_string("Expected %d matched recipients, got %d.",
+ (uint32_t)_factory._expectedMatched.size(),
+ (uint32_t)matched.size())));
+ }
+
+ if (!reply->hasErrors() && _factory._forward) {
+ for (std::vector<Route>::iterator it = matched.begin();
+ it != matched.end(); ++it)
+ {
+ ctx.addChild(*it);
+ }
+ } else {
+ ctx.setReply(std::move(reply));
+ }
+}
+
+void
+CustomPolicy::merge(RoutingContext &ctx)
+{
+ Reply::UP ret(new EmptyReply());
+ for (RoutingNodeIterator it = ctx.getChildIterator();
+ it.isValid(); it.next())
+ {
+ const Reply &reply = it.getReplyRef();
+ for (uint32_t i = 0; i < reply.getNumErrors(); ++i) {
+ ret->addError(reply.getError(i));
+ }
+ }
+ ctx.setReply(std::move(ret));
+}
+
+CustomPolicyFactory::CustomPolicyFactory(bool forward,
+ const std::vector<string> &all,
+ const std::vector<string> &matched) :
+ _forward(forward),
+ _expectedAll(all),
+ _expectedMatched(matched)
+{
+ // empty
+}
+
+IRoutingPolicy::UP
+CustomPolicyFactory::create(const string &)
+{
+ return IRoutingPolicy::UP(new CustomPolicy(*this));
+}
+
+Message::UP
+createMessage(const string &msg)
+{
+ Message::UP ret(new SimpleMessage(msg));
+ ret->getTrace().setLevel(9);
+ return ret;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class TestData {
+public:
+ Slobrok _slobrok;
+ RetryTransientErrorsPolicy::SP _retryPolicy;
+ TestServer _srcServer;
+ SourceSession::UP _srcSession;
+ Receptor _srcHandler;
+ TestServer _dstServer;
+ DestinationSession::UP _dstSession;
+ Receptor _dstHandler;
+
+public:
+ TestData();
+ bool start();
+};
+
+class Test : public vespalib::TestApp {
+private:
+ Message::UP createMessage(const string &msg);
+
+public:
+ int Main();
+ void testSingleDirective(TestData &data);
+ void testMoreDirectives(TestData &data);
+ void testRecipientsRemain(TestData &data);
+ void testConstRoute(TestData &data);
+};
+
+TEST_APPHOOK(Test);
+
+TestData::TestData() :
+ _slobrok(),
+ _retryPolicy(new RetryTransientErrorsPolicy()),
+ _srcServer(MessageBusParams().setRetryPolicy(_retryPolicy).addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setSlobrokConfig(_slobrok.config())),
+ _srcSession(),
+ _srcHandler(),
+ _dstServer(MessageBusParams().addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setIdentity(Identity("dst")).setSlobrokConfig(_slobrok.config())),
+ _dstSession(),
+ _dstHandler()
+{
+ _retryPolicy->setBaseDelay(0);
+}
+
+bool
+TestData::start()
+{
+ _srcSession = _srcServer.mb.createSourceSession(SourceSessionParams().setReplyHandler(_srcHandler));
+ if (_srcSession.get() == NULL) {
+ return false;
+ }
+ _dstSession = _dstServer.mb.createDestinationSession(DestinationSessionParams().setName("session").setMessageHandler(_dstHandler));
+ if (_dstSession.get() == NULL) {
+ return false;
+ }
+ if (!_srcServer.waitSlobrok("dst/session", 1u)) {
+ return false;
+ }
+ return true;
+}
+
+Message::UP
+Test::createMessage(const string &msg)
+{
+ Message::UP ret(new SimpleMessage(msg));
+ ret->getTrace().setLevel(9);
+ return ret;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("routingcontext_test");
+
+ TestData data;
+ ASSERT_TRUE(data.start());
+
+ testSingleDirective(data); TEST_FLUSH();
+ testMoreDirectives(data); TEST_FLUSH();
+ testRecipientsRemain(data); TEST_FLUSH();
+ testConstRoute(data); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test::testSingleDirective(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(
+ false,
+ StringList().add("foo").add("bar").add("baz/cox"),
+ StringList().add("foo").add("bar"))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("myroute").addHop("myhop"))
+ .addHop(HopSpec("myhop", "[Custom]")
+ .addRecipient("foo")
+ .addRecipient("bar")
+ .addRecipient("baz/cox"))));
+ for (uint32_t i = 0; i < 2; ++i) {
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), "myroute").isAccepted());
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ }
+}
+
+void
+Test::testMoreDirectives(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("Custom", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(
+ false,
+ StringList().add("foo").add("foo/bar").add("foo/bar0/baz").add("foo/bar1/baz").add("foo/bar/baz/cox"),
+ StringList().add("foo/bar0/baz").add("foo/bar1/baz"))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("myroute").addHop("myhop"))
+ .addHop(HopSpec("myhop", "foo/[Custom]/baz")
+ .addRecipient("foo")
+ .addRecipient("foo/bar")
+ .addRecipient("foo/bar0/baz")
+ .addRecipient("foo/bar1/baz")
+ .addRecipient("foo/bar/baz/cox"))));
+ for (uint32_t i = 0; i < 2; ++i) {
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), "myroute").isAccepted());
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ }
+}
+
+void
+Test::testRecipientsRemain(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("First", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(
+ true,
+ StringList().add("foo/bar"),
+ StringList().add("foo/[Second]"))));
+ simple.addPolicyFactory("Second", SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(
+ false,
+ StringList().add("foo/bar"),
+ StringList().add("foo/bar"))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("myroute").addHop("myhop"))
+ .addHop(HopSpec("myhop", "[First]/[Second]")
+ .addRecipient("foo/bar"))));
+ for (uint32_t i = 0; i < 2; ++i) {
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), "myroute").isAccepted());
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ }
+}
+
+void
+Test::testConstRoute(TestData &data)
+{
+ IProtocol::SP protocol(new SimpleProtocol());
+ SimpleProtocol &simple = static_cast<SimpleProtocol&>(*protocol);
+ simple.addPolicyFactory("DocumentRouteSelector",
+ SimpleProtocol::IPolicyFactory::SP(new CustomPolicyFactory(
+ true,
+ StringList().add("dst"),
+ StringList().add("dst"))));
+ data._srcServer.mb.putProtocol(protocol);
+ data._srcServer.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addRoute(RouteSpec("default").addHop("indexing"))
+ .addHop(HopSpec("indexing", "[DocumentRouteSelector]").addRecipient("dst"))
+ .addHop(HopSpec("dst", "dst/session"))));
+ for (uint32_t i = 0; i < 2; ++i) {
+ EXPECT_TRUE(data._srcSession->send(createMessage("msg"), Route::parse("route:default")).isAccepted());
+ Message::UP msg = data._dstHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ data._dstSession->acknowledge(std::move(msg));
+ Reply::UP reply = data._srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ printf("%s", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+ }
+}
+
diff --git a/messagebus/src/tests/routingspec/.gitignore b/messagebus/src/tests/routingspec/.gitignore
new file mode 100644
index 00000000000..cd168e7016f
--- /dev/null
+++ b/messagebus/src/tests/routingspec/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+routingspec_test
+messagebus_routingspec_test_app
diff --git a/messagebus/src/tests/routingspec/CMakeLists.txt b/messagebus/src/tests/routingspec/CMakeLists.txt
new file mode 100644
index 00000000000..43539e07af5
--- /dev/null
+++ b/messagebus/src/tests/routingspec/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_routingspec_test_app
+ SOURCES
+ routingspec.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_routingspec_test_app COMMAND messagebus_routingspec_test_app)
diff --git a/messagebus/src/tests/routingspec/DESC b/messagebus/src/tests/routingspec/DESC
new file mode 100644
index 00000000000..28d7f54decc
--- /dev/null
+++ b/messagebus/src/tests/routingspec/DESC
@@ -0,0 +1 @@
+routingspec test. Take a look at routingspec.cpp for details.
diff --git a/messagebus/src/tests/routingspec/FILES b/messagebus/src/tests/routingspec/FILES
new file mode 100644
index 00000000000..4ae228ad5b9
--- /dev/null
+++ b/messagebus/src/tests/routingspec/FILES
@@ -0,0 +1 @@
+routingspec.cpp
diff --git a/messagebus/src/tests/routingspec/routingspec.cpp b/messagebus/src/tests/routingspec/routingspec.cpp
new file mode 100644
index 00000000000..d5317dc3bb0
--- /dev/null
+++ b/messagebus/src/tests/routingspec/routingspec.cpp
@@ -0,0 +1,251 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("routingspec_test");
+
+#include <vespa/config/config.h>
+#include <vespa/messagebus/configagent.h>
+#include <vespa/messagebus/iconfighandler.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/config-messagebus.h>
+
+using namespace mbus;
+using namespace messagebus;
+using namespace config;
+
+class ConfigStore : public IConfigHandler {
+private:
+ RoutingSpec _routing;
+
+public:
+ ConfigStore() : _routing() {
+ // empty
+ }
+
+ bool setupRouting(const RoutingSpec &spec) {
+ _routing = spec;
+ return true;
+ }
+
+ const RoutingSpec &getRoutingSpec() {
+ return _routing;
+ }
+};
+
+class Test : public vespalib::TestApp {
+private:
+ bool testRouting(const RoutingSpec &spec);
+ bool testConfig(const RoutingSpec &spec);
+
+public:
+ void testConstructors();
+ void testConfigGeneration();
+ int Main();
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("routingspec_test");
+
+ testConstructors(); TEST_FLUSH();
+ testConfigGeneration(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+void
+Test::testConstructors()
+{
+ {
+ RoutingSpec spec;
+ spec.addTable(RoutingTableSpec("foo"));
+ spec.getTable(0).addHop(HopSpec("foo-h1", "foo-h1-sel"));
+ spec.getTable(0).getHop(0).addRecipient("foo-h1-r1");
+ spec.getTable(0).getHop(0).addRecipient("foo-h1-r2");
+ spec.getTable(0).addHop(HopSpec("foo-h2", "foo-h2-sel"));
+ spec.getTable(0).getHop(1).addRecipient("foo-h2-r1");
+ spec.getTable(0).getHop(1).addRecipient("foo-h2-r2");
+ spec.getTable(0).addRoute(RouteSpec("foo-r1"));
+ spec.getTable(0).getRoute(0).addHop("foo-h1");
+ spec.getTable(0).getRoute(0).addHop("foo-h2");
+ spec.getTable(0).addRoute(RouteSpec("foo-r2"));
+ spec.getTable(0).getRoute(1).addHop("foo-h2");
+ spec.getTable(0).getRoute(1).addHop("foo-h1");
+ spec.addTable(RoutingTableSpec("bar"));
+ spec.getTable(1).addHop(HopSpec("bar-h1", "bar-h1-sel"));
+ spec.getTable(1).getHop(0).addRecipient("bar-h1-r1");
+ spec.getTable(1).getHop(0).addRecipient("bar-h1-r2");
+ spec.getTable(1).addHop(HopSpec("bar-h2", "bar-h2-sel"));
+ spec.getTable(1).getHop(1).addRecipient("bar-h2-r1");
+ spec.getTable(1).getHop(1).addRecipient("bar-h2-r2");
+ spec.getTable(1).addRoute(RouteSpec("bar-r1"));
+ spec.getTable(1).getRoute(0).addHop("bar-h1");
+ spec.getTable(1).getRoute(0).addHop("bar-h2");
+ spec.getTable(1).addRoute(RouteSpec("bar-r2"));
+ spec.getTable(1).getRoute(1).addHop("bar-h2");
+ spec.getTable(1).getRoute(1).addHop("bar-h1");
+ EXPECT_TRUE(testRouting(spec));
+
+ RoutingSpec specCopy = spec;
+ EXPECT_TRUE(testRouting(specCopy));
+ }
+ {
+ RoutingSpec spec = RoutingSpec()
+ .addTable(RoutingTableSpec("foo")
+ .addHop(HopSpec("foo-h1", "foo-h1-sel")
+ .addRecipient("foo-h1-r1")
+ .addRecipient("foo-h1-r2"))
+ .addHop(HopSpec("foo-h2", "foo-h2-sel")
+ .addRecipient("foo-h2-r1")
+ .addRecipient("foo-h2-r2"))
+ .addRoute(RouteSpec("foo-r1")
+ .addHop("foo-h1")
+ .addHop("foo-h2"))
+ .addRoute(RouteSpec("foo-r2")
+ .addHop("foo-h2")
+ .addHop("foo-h1")))
+ .addTable(RoutingTableSpec("bar")
+ .addHop(HopSpec("bar-h1", "bar-h1-sel")
+ .addRecipient("bar-h1-r1")
+ .addRecipient("bar-h1-r2"))
+ .addHop(HopSpec("bar-h2", "bar-h2-sel")
+ .addRecipient("bar-h2-r1")
+ .addRecipient("bar-h2-r2"))
+ .addRoute(RouteSpec("bar-r1")
+ .addHop("bar-h1")
+ .addHop("bar-h2"))
+ .addRoute(RouteSpec("bar-r2")
+ .addHop("bar-h2")
+ .addHop("bar-h1")));
+ EXPECT_TRUE(testRouting(spec));
+
+ RoutingSpec specCopy = spec;
+ EXPECT_TRUE(testRouting(specCopy));
+ }
+}
+
+bool
+Test::testRouting(const RoutingSpec &spec)
+{
+ if (!ASSERT_TRUE(spec.getNumTables() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getProtocol() == "foo")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(0).getNumHops() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(0).getName() == "foo-h1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(0).getSelector() == "foo-h1-sel")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(0).getHop(0).getNumRecipients() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(0).getRecipient(0) == "foo-h1-r1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(0).getRecipient(1) == "foo-h1-r2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(1).getName() == "foo-h2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(1).getSelector() == "foo-h2-sel")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(0).getHop(1).getNumRecipients() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(1).getRecipient(0) == "foo-h2-r1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getHop(1).getRecipient(1) == "foo-h2-r2")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(0).getNumRoutes() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getRoute(0).getName() == "foo-r1")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(0).getRoute(0).getNumHops() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getRoute(0).getHop(0) == "foo-h1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getRoute(0).getHop(1) == "foo-h2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getRoute(1).getName() == "foo-r2")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(0).getRoute(1).getNumHops() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getRoute(1).getHop(0) == "foo-h2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(0).getRoute(1).getHop(1) == "foo-h1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getProtocol() == "bar")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(1).getNumHops() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(0).getName() == "bar-h1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(0).getSelector() == "bar-h1-sel")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(1).getHop(0).getNumRecipients() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(0).getRecipient(0) == "bar-h1-r1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(0).getRecipient(1) == "bar-h1-r2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(1).getName() == "bar-h2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(1).getSelector() == "bar-h2-sel")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(1).getHop(1).getNumRecipients() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(1).getRecipient(0) == "bar-h2-r1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getHop(1).getRecipient(1) == "bar-h2-r2")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(1).getNumRoutes() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getRoute(0).getName() == "bar-r1")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(1).getRoute(0).getNumHops() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getRoute(0).getHop(0) == "bar-h1")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getRoute(0).getHop(1) == "bar-h2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getRoute(1).getName() == "bar-r2")) { return false; }
+ if (!ASSERT_TRUE(spec.getTable(1).getRoute(1).getNumHops() == 2)) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getRoute(1).getHop(0) == "bar-h2")) { return false; }
+ if (!EXPECT_TRUE(spec.getTable(1).getRoute(1).getHop(1) == "bar-h1")) { return false; }
+ return true;
+}
+
+void
+Test::testConfigGeneration()
+{
+ EXPECT_TRUE(testConfig(RoutingSpec()));
+ EXPECT_TRUE(testConfig(RoutingSpec().addTable(RoutingTableSpec("mytable1"))));
+ EXPECT_TRUE(testConfig(RoutingSpec().addTable(RoutingTableSpec("mytable1")
+ .addHop(HopSpec("myhop1", "myselector1")))));
+ EXPECT_TRUE(testConfig(RoutingSpec().addTable(RoutingTableSpec("mytable1")
+ .addHop(HopSpec("myhop1", "myselector1"))
+ .addRoute(RouteSpec("myroute1").addHop("myhop1")))));
+ EXPECT_TRUE(testConfig(RoutingSpec().addTable(RoutingTableSpec("mytable1")
+ .addHop(HopSpec("myhop1", "myselector1"))
+ .addHop(HopSpec("myhop2", "myselector2"))
+ .addRoute(RouteSpec("myroute1").addHop("myhop1"))
+ .addRoute(RouteSpec("myroute2").addHop("myhop2"))
+ .addRoute(RouteSpec("myroute12").addHop("myhop1").addHop("myhop2")))));
+ EXPECT_TRUE(testConfig(RoutingSpec()
+ .addTable(RoutingTableSpec("mytable1")
+ .addHop(HopSpec("myhop1", "myselector1"))
+ .addHop(HopSpec("myhop2", "myselector2"))
+ .addRoute(RouteSpec("myroute1").addHop("myhop1"))
+ .addRoute(RouteSpec("myroute2").addHop("myhop2"))
+ .addRoute(RouteSpec("myroute12").addHop("myhop1").addHop("myhop2")))
+ .addTable(RoutingTableSpec("mytable2"))));
+
+ EXPECT_EQUAL("routingtable[2]\n"
+ "routingtable[0].protocol \"mytable1\"\n"
+ "routingtable[1].protocol \"mytable2\"\n"
+ "routingtable[1].hop[3]\n"
+ "routingtable[1].hop[0].name \"myhop1\"\n"
+ "routingtable[1].hop[0].selector \"myselector1\"\n"
+ "routingtable[1].hop[1].name \"myhop2\"\n"
+ "routingtable[1].hop[1].selector \"myselector2\"\n"
+ "routingtable[1].hop[1].ignoreresult true\n"
+ "routingtable[1].hop[2].name \"myhop1\"\n"
+ "routingtable[1].hop[2].selector \"myselector3\"\n"
+ "routingtable[1].hop[2].recipient[2]\n"
+ "routingtable[1].hop[2].recipient[0] \"myrecipient1\"\n"
+ "routingtable[1].hop[2].recipient[1] \"myrecipient2\"\n"
+ "routingtable[1].route[1]\n"
+ "routingtable[1].route[0].name \"myroute1\"\n"
+ "routingtable[1].route[0].hop[1]\n"
+ "routingtable[1].route[0].hop[0] \"myhop1\"\n",
+ RoutingSpec()
+ .addTable(RoutingTableSpec("mytable1"))
+ .addTable(RoutingTableSpec("mytable2")
+ .addHop(HopSpec("myhop1", "myselector1"))
+ .addHop(HopSpec("myhop2", "myselector2").setIgnoreResult(true))
+ .addHop(HopSpec("myhop1", "myselector3")
+ .addRecipient("myrecipient1")
+ .addRecipient("myrecipient2"))
+ .addRoute(RouteSpec("myroute1").addHop("myhop1"))).toString());
+}
+
+bool
+Test::testConfig(const RoutingSpec &spec)
+{
+ if (!EXPECT_TRUE(spec == spec)) {
+ return false;
+ }
+ if (!EXPECT_TRUE(spec == RoutingSpec(spec))) {
+ return false;
+ }
+ ConfigStore store;
+ ConfigAgent agent(store);
+ agent.configure(ConfigGetter<MessagebusConfig>().getConfig("", RawSpec(spec.toString())));
+ if (!EXPECT_TRUE(store.getRoutingSpec() == spec)) {
+ return false;
+ }
+ return true;
+}
+
diff --git a/messagebus/src/tests/rpcserviceaddress/.gitignore b/messagebus/src/tests/rpcserviceaddress/.gitignore
new file mode 100644
index 00000000000..fcb95aca804
--- /dev/null
+++ b/messagebus/src/tests/rpcserviceaddress/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+rpcserviceaddress_test
+messagebus_rpcserviceaddress_test_app
diff --git a/messagebus/src/tests/rpcserviceaddress/CMakeLists.txt b/messagebus/src/tests/rpcserviceaddress/CMakeLists.txt
new file mode 100644
index 00000000000..e4ada1c8c1b
--- /dev/null
+++ b/messagebus/src/tests/rpcserviceaddress/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_rpcserviceaddress_test_app
+ SOURCES
+ rpcserviceaddress.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_rpcserviceaddress_test_app COMMAND messagebus_rpcserviceaddress_test_app)
diff --git a/messagebus/src/tests/rpcserviceaddress/DESC b/messagebus/src/tests/rpcserviceaddress/DESC
new file mode 100644
index 00000000000..2c0f5565509
--- /dev/null
+++ b/messagebus/src/tests/rpcserviceaddress/DESC
@@ -0,0 +1 @@
+rpcserviceaddress test. Take a look at rpcserviceaddress.cpp for details.
diff --git a/messagebus/src/tests/rpcserviceaddress/FILES b/messagebus/src/tests/rpcserviceaddress/FILES
new file mode 100644
index 00000000000..ea9edf09a87
--- /dev/null
+++ b/messagebus/src/tests/rpcserviceaddress/FILES
@@ -0,0 +1 @@
+rpcserviceaddress.cpp
diff --git a/messagebus/src/tests/rpcserviceaddress/rpcserviceaddress.cpp b/messagebus/src/tests/rpcserviceaddress/rpcserviceaddress.cpp
new file mode 100644
index 00000000000..d5a002adf89
--- /dev/null
+++ b/messagebus/src/tests/rpcserviceaddress/rpcserviceaddress.cpp
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpcserviceaddress_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/network/rpcserviceaddress.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("rpcserviceaddress_test");
+ {
+ EXPECT_TRUE(RPCServiceAddress("", "bar").isMalformed());
+ EXPECT_TRUE(RPCServiceAddress("foo", "bar").isMalformed());
+ EXPECT_TRUE(RPCServiceAddress("foo/", "bar").isMalformed());
+ EXPECT_TRUE(RPCServiceAddress("/foo", "bar").isMalformed());
+ }
+ {
+ RPCServiceAddress addr("foo/bar/baz", "tcp/foo.com:42");
+ EXPECT_TRUE(!addr.isMalformed());
+ EXPECT_TRUE(addr.getServiceName() == "foo/bar/baz");
+ EXPECT_TRUE(addr.getConnectionSpec() == "tcp/foo.com:42");
+ EXPECT_TRUE(addr.getSessionName() == "baz");
+ }
+ {
+ RPCServiceAddress addr("foo/bar", "tcp/foo.com:42");
+ EXPECT_TRUE(!addr.isMalformed());
+ EXPECT_TRUE(addr.getServiceName() == "foo/bar");
+ EXPECT_TRUE(addr.getConnectionSpec() == "tcp/foo.com:42");
+ EXPECT_TRUE(addr.getSessionName() == "bar");
+ }
+ {
+ RPCServiceAddress addr("", "tcp/foo.com:42");
+ EXPECT_TRUE(addr.isMalformed());
+ EXPECT_TRUE(addr.getServiceName() == "");
+ EXPECT_TRUE(addr.getConnectionSpec() == "tcp/foo.com:42");
+ EXPECT_TRUE(addr.getSessionName() == "");
+ }
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/selector/.gitignore b/messagebus/src/tests/selector/.gitignore
new file mode 100644
index 00000000000..8ffe3b2ac91
--- /dev/null
+++ b/messagebus/src/tests/selector/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+selector_test
diff --git a/messagebus/src/tests/sendadapter/.gitignore b/messagebus/src/tests/sendadapter/.gitignore
new file mode 100644
index 00000000000..7a13e0d4ee1
--- /dev/null
+++ b/messagebus/src/tests/sendadapter/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+sendadapter_test
+messagebus_sendadapter_test_app
diff --git a/messagebus/src/tests/sendadapter/CMakeLists.txt b/messagebus/src/tests/sendadapter/CMakeLists.txt
new file mode 100644
index 00000000000..32c41b40c3c
--- /dev/null
+++ b/messagebus/src/tests/sendadapter/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_sendadapter_test_app
+ SOURCES
+ sendadapter.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_sendadapter_test_app COMMAND messagebus_sendadapter_test_app)
diff --git a/messagebus/src/tests/sendadapter/DESC b/messagebus/src/tests/sendadapter/DESC
new file mode 100644
index 00000000000..35a50283921
--- /dev/null
+++ b/messagebus/src/tests/sendadapter/DESC
@@ -0,0 +1 @@
+sendadapter test. Take a look at sendadapter.cpp for details.
diff --git a/messagebus/src/tests/sendadapter/FILES b/messagebus/src/tests/sendadapter/FILES
new file mode 100644
index 00000000000..c43cbb6a53c
--- /dev/null
+++ b/messagebus/src/tests/sendadapter/FILES
@@ -0,0 +1 @@
+sendadapter.cpp
diff --git a/messagebus/src/tests/sendadapter/sendadapter.cpp b/messagebus/src/tests/sendadapter/sendadapter.cpp
new file mode 100644
index 00000000000..b25240acdac
--- /dev/null
+++ b/messagebus/src/tests/sendadapter/sendadapter.cpp
@@ -0,0 +1,252 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("sendadapter_test");
+
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class TestProtocol : public mbus::SimpleProtocol {
+private:
+ mutable vespalib::Version _lastVersion;
+
+public:
+ typedef std::shared_ptr<TestProtocol> SP;
+ mbus::Blob encode(const vespalib::Version &version, const mbus::Routable &routable) const {
+ _lastVersion = version;
+ return mbus::SimpleProtocol::encode(version, routable);
+ }
+ mbus::Routable::UP decode(const vespalib::Version &version, mbus::BlobRef blob) const {
+ _lastVersion = version;
+ return mbus::SimpleProtocol::decode(version, blob);
+ }
+ const vespalib::Version &getLastVersion() { return _lastVersion; }
+};
+
+class TestData {
+public:
+ Slobrok _slobrok;
+ TestProtocol::SP _srcProtocol;
+ TestServer _srcServer;
+ SourceSession::UP _srcSession;
+ Receptor _srcHandler;
+ TestProtocol::SP _itrProtocol;
+ TestServer _itrServer;
+ IntermediateSession::UP _itrSession;
+ Receptor _itrHandler;
+ TestProtocol::SP _dstProtocol;
+ TestServer _dstServer;
+ DestinationSession::UP _dstSession;
+ Receptor _dstHandler;
+
+public:
+ TestData();
+ bool start();
+};
+
+class Test : public vespalib::TestApp {
+private:
+ static const int TIMEOUT_SECS = 60;
+
+ bool testVersionedSend(TestData &data,
+ const vespalib::Version &srcVersion,
+ const vespalib::Version &itrVersion,
+ const vespalib::Version &dstVersion);
+ void testSendAdapters(TestData &data);
+
+public:
+ int Main();
+};
+
+TEST_APPHOOK(Test);
+
+TestData::TestData() :
+ _slobrok(),
+ _srcProtocol(new TestProtocol()),
+ _srcServer(MessageBusParams().setRetryPolicy(IRetryPolicy::SP()).addProtocol(_srcProtocol),
+ RPCNetworkParams().setSlobrokConfig(_slobrok.config())),
+ _srcSession(),
+ _srcHandler(),
+ _itrProtocol(new TestProtocol()),
+ _itrServer(MessageBusParams().addProtocol(_itrProtocol),
+ RPCNetworkParams().setIdentity(Identity("itr")).setSlobrokConfig(_slobrok.config())),
+ _itrSession(),
+ _itrHandler(),
+ _dstProtocol(new TestProtocol()),
+ _dstServer(MessageBusParams().addProtocol(_dstProtocol),
+ RPCNetworkParams().setIdentity(Identity("dst")).setSlobrokConfig(_slobrok.config())),
+ _dstSession(),
+ _dstHandler()
+{
+ // empty
+}
+
+bool
+TestData::start()
+{
+ _srcSession = _srcServer.mb.createSourceSession(SourceSessionParams().setReplyHandler(_srcHandler));
+ if (_srcSession.get() == NULL) {
+ return false;
+ }
+ _itrSession = _itrServer.mb.createIntermediateSession(IntermediateSessionParams().setName("session").setMessageHandler(_itrHandler).setReplyHandler(_itrHandler));
+ if (_itrSession.get() == NULL) {
+ return false;
+ }
+ _dstSession = _dstServer.mb.createDestinationSession(DestinationSessionParams().setName("session").setMessageHandler(_dstHandler));
+ if (_dstSession.get() == NULL) {
+ return false;
+ }
+ if (!_srcServer.waitSlobrok("*/session", 2u)) {
+ return false;
+ }
+ return true;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("sendadapter_test");
+
+ TestData data;
+ ASSERT_TRUE(data.start());
+
+ testSendAdapters(data); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test::testSendAdapters(TestData &data)
+{
+ std::vector<vespalib::Version> versions;
+ versions.push_back(vespalib::Version(5, 0));
+ versions.push_back(vespalib::Version(5, 1));
+
+ for (std::vector<vespalib::Version>::const_iterator srcVersion = versions.begin();
+ srcVersion != versions.end(); ++srcVersion)
+ {
+ for (std::vector<vespalib::Version>::const_iterator itrVersion = versions.begin();
+ itrVersion != versions.end(); ++itrVersion)
+ {
+ for (std::vector<vespalib::Version>::const_iterator dstVersion = versions.begin();
+ dstVersion != versions.end(); ++dstVersion)
+ {
+ EXPECT_TRUE(testVersionedSend(data, *srcVersion, *itrVersion, *dstVersion));
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+////////////////////////////////////////////////////////////////////////////////
+
+bool
+Test::testVersionedSend(TestData &data,
+ const vespalib::Version &srcVersion,
+ const vespalib::Version &itrVersion,
+ const vespalib::Version &dstVersion)
+{
+ LOG(info, "Sending from %s through %s to %s.",
+ srcVersion.toString().c_str(), itrVersion.toString().c_str(), dstVersion.toString().c_str());
+ data._srcServer.net.setVersion(srcVersion);
+ data._itrServer.net.setVersion(itrVersion);
+ data._dstServer.net.setVersion(dstVersion);
+
+ Message::UP msg(new SimpleMessage("foo"));
+ msg->getTrace().setLevel(9);
+ if (!EXPECT_TRUE(data._srcSession->send(std::move(msg), Route::parse("itr/session dst/session")).isAccepted())) {
+ return false;
+ }
+ msg = data._itrHandler.getMessage(TIMEOUT_SECS);
+ if (!EXPECT_TRUE(msg.get() != NULL)) {
+ return false;
+ }
+ LOG(info, "Message version %s serialized at source.",
+ data._srcProtocol->getLastVersion().toString().c_str());
+ vespalib::Version minVersion = std::min(srcVersion, itrVersion);
+ if (!EXPECT_TRUE(minVersion == data._srcProtocol->getLastVersion())) {
+ return false;
+ }
+
+ LOG(info, "Message version %s reached intermediate.",
+ data._itrProtocol->getLastVersion().toString().c_str());
+ if (!EXPECT_TRUE(minVersion == data._itrProtocol->getLastVersion())) {
+ return false;
+ }
+ data._itrSession->forward(std::move(msg));
+ msg = data._dstHandler.getMessage(TIMEOUT_SECS);
+ if (!EXPECT_TRUE(msg.get() != NULL)) {
+ return false;
+ }
+ LOG(info, "Message version %s serialized at intermediate.",
+ data._itrProtocol->getLastVersion().toString().c_str());
+ minVersion = std::min(itrVersion, dstVersion);
+ if (!EXPECT_TRUE(minVersion == data._itrProtocol->getLastVersion())) {
+ return false;
+ }
+
+ LOG(info, "Message version %s reached destination.",
+ data._dstProtocol->getLastVersion().toString().c_str());
+ if (!EXPECT_TRUE(minVersion == data._dstProtocol->getLastVersion())) {
+ return false;
+ }
+ Reply::UP reply(new SimpleReply("bar"));
+ reply->swapState(*msg);
+ data._dstSession->reply(std::move(reply));
+ reply = data._itrHandler.getReply();
+ if (!EXPECT_TRUE(reply.get() != NULL)) {
+ return false;
+ }
+ LOG(info, "Reply version %s serialized at destination.",
+ data._dstProtocol->getLastVersion().toString().c_str());
+ if (!EXPECT_TRUE(minVersion == data._dstProtocol->getLastVersion())) {
+ return false;
+ }
+
+ LOG(info, "Reply version %s reached intermediate.",
+ data._itrProtocol->getLastVersion().toString().c_str());
+ if (!EXPECT_TRUE(minVersion == data._itrProtocol->getLastVersion())) {
+ return false;
+ }
+ data._itrSession->forward(std::move(reply));
+ reply = data._srcHandler.getReply();
+ if (!EXPECT_TRUE(reply.get() != NULL)) {
+ return false;
+ }
+ LOG(info, "Reply version %s serialized at intermediate.",
+ data._dstProtocol->getLastVersion().toString().c_str());
+ minVersion = std::min(srcVersion, itrVersion);
+ if (!EXPECT_TRUE(minVersion == data._itrProtocol->getLastVersion())) {
+ return false;
+ }
+
+ LOG(info, "Reply version %s reached source.",
+ data._srcProtocol->getLastVersion().toString().c_str());
+ if (!EXPECT_TRUE(minVersion == data._srcProtocol->getLastVersion())) {
+ return false;
+ }
+ return true;
+}
diff --git a/messagebus/src/tests/sequencer/.gitignore b/messagebus/src/tests/sequencer/.gitignore
new file mode 100644
index 00000000000..cc673f20668
--- /dev/null
+++ b/messagebus/src/tests/sequencer/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+sequencer_test
+messagebus_sequencer_test_app
diff --git a/messagebus/src/tests/sequencer/CMakeLists.txt b/messagebus/src/tests/sequencer/CMakeLists.txt
new file mode 100644
index 00000000000..dab54431722
--- /dev/null
+++ b/messagebus/src/tests/sequencer/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_sequencer_test_app
+ SOURCES
+ sequencer.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_sequencer_test_app COMMAND messagebus_sequencer_test_app)
diff --git a/messagebus/src/tests/sequencer/DESC b/messagebus/src/tests/sequencer/DESC
new file mode 100644
index 00000000000..761c069aa92
--- /dev/null
+++ b/messagebus/src/tests/sequencer/DESC
@@ -0,0 +1 @@
+sequencer test. Take a look at sequencer.cpp for details.
diff --git a/messagebus/src/tests/sequencer/FILES b/messagebus/src/tests/sequencer/FILES
new file mode 100644
index 00000000000..a8d6aeae540
--- /dev/null
+++ b/messagebus/src/tests/sequencer/FILES
@@ -0,0 +1 @@
+sequencer.cpp
diff --git a/messagebus/src/tests/sequencer/sequencer.cpp b/messagebus/src/tests/sequencer/sequencer.cpp
new file mode 100644
index 00000000000..b2818cfa57d
--- /dev/null
+++ b/messagebus/src/tests/sequencer/sequencer.cpp
@@ -0,0 +1,194 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("sequencer_test");
+
+#include <vespa/messagebus/sequencer.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+// --------------------------------------------------------------------------------
+//
+// Setup.
+//
+// --------------------------------------------------------------------------------
+
+struct MyQueue : public RoutableQueue {
+
+ virtual ~MyQueue() {
+ while (size() > 0) {
+ Routable::UP obj = dequeue(0);
+ obj->getCallStack().discard();
+ }
+ }
+
+ bool checkReply(bool hasSeqId, uint64_t seqId) {
+ if (size() == 0) {
+ LOG(error, "checkReply(): No reply in queue.");
+ return false;
+ }
+ Routable::UP obj = dequeue(0);
+ if (!obj->isReply()) {
+ LOG(error, "checkReply(): Got message when expecting reply.");
+ return false;
+ }
+ Reply::UP reply(static_cast<Reply*>(obj.release()));
+ Message::UP msg = reply->getMessage();
+ if (msg.get() == NULL) {
+ LOG(error, "checkReply(): Reply has no message attached.");
+ return false;
+ }
+ if (hasSeqId) {
+ if (!msg->hasSequenceId()) {
+ LOG(error, "checkReply(): Expected sequence id %" PRIu64 ", got none.",
+ seqId);
+ return false;
+ }
+ if (msg->getSequenceId() != seqId) {
+ LOG(error, "checkReply(): Expected sequence id %" PRIu64 ", got %" PRIu64 ".",
+ seqId, msg->getSequenceId());
+ return false;
+ }
+ } else {
+ if (msg->hasSequenceId()) {
+ LOG(error, "checkReply(): Message has unexpected sequence id %" PRIu64 ".",
+ msg->getSequenceId());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void replyNext() {
+ Routable::UP obj = dequeue(0);
+ Message::UP msg(static_cast<Message*>(obj.release()));
+
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->setMessage(std::move(msg));
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+ }
+
+ Message::UP createMessage(bool hasSeqId, uint64_t seqId) {
+ Message::UP ret(new SimpleMessage("foo", hasSeqId, seqId));
+ ret->pushHandler(*this);
+ return ret;
+ }
+};
+
+class Test : public vespalib::TestApp {
+private:
+ void testSyncNone();
+ void testSyncId();
+
+public:
+ int Main() {
+ TEST_INIT("sequencer_test");
+
+ testSyncNone(); TEST_FLUSH();
+ testSyncId(); TEST_FLUSH();
+
+ TEST_DONE();
+ }
+};
+
+TEST_APPHOOK(Test);
+
+// --------------------------------------------------------------------------------
+//
+// Tests.
+//
+// --------------------------------------------------------------------------------
+
+void
+Test::testSyncNone()
+{
+ MyQueue src;
+ MyQueue dst;
+ Sequencer seq(dst);
+
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ seq.handleMessage(src.createMessage(false, 0));
+ EXPECT_EQUAL(0u, src.size());
+ EXPECT_EQUAL(5u, dst.size());
+
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ EXPECT_EQUAL(5u, src.size());
+ EXPECT_EQUAL(0u, dst.size());
+
+ EXPECT_TRUE(src.checkReply(false, 0));
+ EXPECT_TRUE(src.checkReply(false, 0));
+ EXPECT_TRUE(src.checkReply(false, 0));
+ EXPECT_TRUE(src.checkReply(false, 0));
+ EXPECT_TRUE(src.checkReply(false, 0));
+ EXPECT_EQUAL(0u, src.size());
+ EXPECT_EQUAL(0u, dst.size());
+}
+
+void
+Test::testSyncId()
+{
+ MyQueue src;
+ MyQueue dst;
+ Sequencer seq(dst);
+
+ seq.handleMessage(src.createMessage(true, 1));
+ seq.handleMessage(src.createMessage(true, 2));
+ seq.handleMessage(src.createMessage(true, 3));
+ seq.handleMessage(src.createMessage(true, 4));
+ seq.handleMessage(src.createMessage(true, 5));
+ EXPECT_EQUAL(0u, src.size());
+ EXPECT_EQUAL(5u, dst.size());
+
+ seq.handleMessage(src.createMessage(true, 1));
+ seq.handleMessage(src.createMessage(true, 5));
+ seq.handleMessage(src.createMessage(true, 2));
+ seq.handleMessage(src.createMessage(true, 10));
+ seq.handleMessage(src.createMessage(true, 4));
+ seq.handleMessage(src.createMessage(true, 3));
+ EXPECT_EQUAL(0u, src.size());
+ EXPECT_EQUAL(6u, dst.size());
+
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ EXPECT_EQUAL(5u, src.size());
+ EXPECT_EQUAL(6u, dst.size());
+
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ dst.replyNext();
+ EXPECT_EQUAL(11u, src.size());
+ EXPECT_EQUAL(0u, dst.size());
+
+ EXPECT_TRUE(src.checkReply(true, 1));
+ EXPECT_TRUE(src.checkReply(true, 2));
+ EXPECT_TRUE(src.checkReply(true, 3));
+ EXPECT_TRUE(src.checkReply(true, 4));
+ EXPECT_TRUE(src.checkReply(true, 5));
+ EXPECT_TRUE(src.checkReply(true, 10));
+ EXPECT_TRUE(src.checkReply(true, 1));
+ EXPECT_TRUE(src.checkReply(true, 2));
+ EXPECT_TRUE(src.checkReply(true, 3));
+ EXPECT_TRUE(src.checkReply(true, 4));
+ EXPECT_TRUE(src.checkReply(true, 5));
+ EXPECT_EQUAL(0u, src.size());
+ EXPECT_EQUAL(0u, dst.size());
+}
diff --git a/messagebus/src/tests/serviceaddress/.gitignore b/messagebus/src/tests/serviceaddress/.gitignore
new file mode 100644
index 00000000000..62275eb8c04
--- /dev/null
+++ b/messagebus/src/tests/serviceaddress/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+serviceaddress_test
+messagebus_serviceaddress_test_app
diff --git a/messagebus/src/tests/serviceaddress/CMakeLists.txt b/messagebus/src/tests/serviceaddress/CMakeLists.txt
new file mode 100644
index 00000000000..630e28dc94f
--- /dev/null
+++ b/messagebus/src/tests/serviceaddress/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_serviceaddress_test_app
+ SOURCES
+ serviceaddress.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_serviceaddress_test_app COMMAND messagebus_serviceaddress_test_app)
diff --git a/messagebus/src/tests/serviceaddress/DESC b/messagebus/src/tests/serviceaddress/DESC
new file mode 100644
index 00000000000..38fa7c16b1a
--- /dev/null
+++ b/messagebus/src/tests/serviceaddress/DESC
@@ -0,0 +1 @@
+serviceaddress test. Take a look at serviceaddress.cpp for details.
diff --git a/messagebus/src/tests/serviceaddress/FILES b/messagebus/src/tests/serviceaddress/FILES
new file mode 100644
index 00000000000..37e17b66b5e
--- /dev/null
+++ b/messagebus/src/tests/serviceaddress/FILES
@@ -0,0 +1 @@
+serviceaddress.cpp
diff --git a/messagebus/src/tests/serviceaddress/serviceaddress.cpp b/messagebus/src/tests/serviceaddress/serviceaddress.cpp
new file mode 100644
index 00000000000..36d7776f732
--- /dev/null
+++ b/messagebus/src/tests/serviceaddress/serviceaddress.cpp
@@ -0,0 +1,137 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("serviceaddress_test");
+
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/network/rpcservice.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+
+using namespace mbus;
+
+class Test : public vespalib::TestApp {
+public:
+ int Main();
+ void testAddrServiceAddress();
+ void testNameServiceAddress();
+
+private:
+ bool waitSlobrok(RPCNetwork &network, const string &pattern, size_t num);
+ bool testAddress(RPCNetwork& network, const string &pattern,
+ const string &expectedSpec, const string &expectedSession);
+ bool testNullAddress(RPCNetwork &network, const string &pattern);
+};
+
+int
+Test::Main()
+{
+ TEST_INIT("serviceaddress_test");
+
+ testAddrServiceAddress(); TEST_FLUSH();
+ testNameServiceAddress(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+TEST_APPHOOK(Test);
+
+void
+Test::testAddrServiceAddress()
+{
+ Slobrok slobrok;
+ RPCNetwork network(RPCNetworkParams()
+ .setIdentity(Identity("foo"))
+ .setSlobrokConfig(slobrok.config()));
+ ASSERT_TRUE(network.start());
+
+ EXPECT_TRUE(testNullAddress(network, "tcp"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/localhost"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/localhost:"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/localhost:1977"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/localhost:1977/"));
+ EXPECT_TRUE(testAddress(network, "tcp/localhost:1977/session", "tcp/localhost:1977", "session"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/localhost:/session"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/:1977/session"));
+ EXPECT_TRUE(testNullAddress(network, "tcp/:/session"));
+
+ network.shutdown();
+}
+
+void
+Test::testNameServiceAddress()
+{
+ Slobrok slobrok;
+ RPCNetwork network(RPCNetworkParams()
+ .setIdentity(Identity("foo"))
+ .setSlobrokConfig(slobrok.config()));
+ ASSERT_TRUE(network.start());
+
+ network.unregisterSession("session");
+ ASSERT_TRUE(waitSlobrok(network, "foo/session", 0));
+ EXPECT_TRUE(testNullAddress(network, "foo/session"));
+
+ network.registerSession("session");
+ ASSERT_TRUE(waitSlobrok(network, "foo/session", 1));
+ EXPECT_TRUE(testAddress(network, "foo/session", network.getConnectionSpec().c_str(), "session"));
+
+ network.shutdown();
+}
+
+bool
+Test::waitSlobrok(RPCNetwork &network, const string &pattern, size_t num)
+{
+ for (int i = 0; i < 1000; i++) {
+ slobrok::api::MirrorAPI::SpecList res = network.getMirror().lookup(pattern);
+ if (res.size() == num) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ return false;
+}
+
+bool
+Test::testNullAddress(RPCNetwork &network, const string &pattern)
+{
+ RPCService service(network.getMirror(), pattern);
+ RPCServiceAddress::UP obj = service.resolve();
+ if (!EXPECT_TRUE(obj.get() == NULL)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+Test::testAddress(RPCNetwork &network, const string &pattern,
+ const string &expectedSpec, const string &expectedSession)
+{
+ RPCService service(network.getMirror(), pattern);
+ RPCServiceAddress::UP obj = service.resolve();
+ if (!EXPECT_TRUE(obj.get() != NULL)) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(expectedSpec, obj->getConnectionSpec())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL(expectedSession, obj->getSessionName())) {
+ return false;
+ }
+ return true;
+}
+
diff --git a/messagebus/src/tests/servicepool/.gitignore b/messagebus/src/tests/servicepool/.gitignore
new file mode 100644
index 00000000000..d8b2f3b9b09
--- /dev/null
+++ b/messagebus/src/tests/servicepool/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+servicepool_test
+messagebus_servicepool_test_app
diff --git a/messagebus/src/tests/servicepool/CMakeLists.txt b/messagebus/src/tests/servicepool/CMakeLists.txt
new file mode 100644
index 00000000000..0d6cbc54862
--- /dev/null
+++ b/messagebus/src/tests/servicepool/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_servicepool_test_app
+ SOURCES
+ servicepool.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_servicepool_test_app COMMAND messagebus_servicepool_test_app)
diff --git a/messagebus/src/tests/servicepool/DESC b/messagebus/src/tests/servicepool/DESC
new file mode 100644
index 00000000000..21484039b7a
--- /dev/null
+++ b/messagebus/src/tests/servicepool/DESC
@@ -0,0 +1 @@
+servicepool test. Take a look at servicepool.cpp for details.
diff --git a/messagebus/src/tests/servicepool/FILES b/messagebus/src/tests/servicepool/FILES
new file mode 100644
index 00000000000..22d1bbb2ba8
--- /dev/null
+++ b/messagebus/src/tests/servicepool/FILES
@@ -0,0 +1 @@
+servicepool.cpp
diff --git a/messagebus/src/tests/servicepool/servicepool.cpp b/messagebus/src/tests/servicepool/servicepool.cpp
new file mode 100644
index 00000000000..5cf4b8b6132
--- /dev/null
+++ b/messagebus/src/tests/servicepool/servicepool.cpp
@@ -0,0 +1,71 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("servicepool_test");
+
+#include <vespa/messagebus/network/rpcnetwork.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+class Test : public vespalib::TestApp {
+private:
+ void testMaxSize();
+
+public:
+ int Main() {
+ TEST_INIT("servicepool_test");
+
+ testMaxSize(); TEST_FLUSH();
+
+ TEST_DONE();
+ }
+};
+
+TEST_APPHOOK(Test);
+
+void
+Test::testMaxSize()
+{
+ Slobrok slobrok;
+ RPCNetwork net(RPCNetworkParams().setSlobrokConfig(slobrok.config()));
+ RPCServicePool pool(net, 2);
+ net.start();
+
+ pool.resolve("foo");
+ EXPECT_EQUAL(1u, pool.getSize());
+ EXPECT_TRUE(pool.hasService("foo"));
+ EXPECT_TRUE(!pool.hasService("bar"));
+ EXPECT_TRUE(!pool.hasService("baz"));
+
+ pool.resolve("foo");
+ EXPECT_EQUAL(1u, pool.getSize());
+ EXPECT_TRUE(pool.hasService("foo"));
+ EXPECT_TRUE(!pool.hasService("bar"));
+ EXPECT_TRUE(!pool.hasService("baz"));
+
+ pool.resolve("bar");
+ EXPECT_EQUAL(2u, pool.getSize());
+ EXPECT_TRUE(pool.hasService("foo"));
+ EXPECT_TRUE(pool.hasService("bar"));
+ EXPECT_TRUE(!pool.hasService("baz"));
+
+ pool.resolve("baz");
+ EXPECT_EQUAL(2u, pool.getSize());
+ EXPECT_TRUE(!pool.hasService("foo"));
+ EXPECT_TRUE(pool.hasService("bar"));
+ EXPECT_TRUE(pool.hasService("baz"));
+
+ pool.resolve("bar");
+ EXPECT_EQUAL(2u, pool.getSize());
+ EXPECT_TRUE(!pool.hasService("foo"));
+ EXPECT_TRUE(pool.hasService("bar"));
+ EXPECT_TRUE(pool.hasService("baz"));
+
+ pool.resolve("foo");
+ EXPECT_EQUAL(2u, pool.getSize());
+ EXPECT_TRUE(pool.hasService("foo"));
+ EXPECT_TRUE(pool.hasService("bar"));
+ EXPECT_TRUE(!pool.hasService("baz"));
+}
diff --git a/messagebus/src/tests/shutdown/.gitignore b/messagebus/src/tests/shutdown/.gitignore
new file mode 100644
index 00000000000..f3a6f7e0061
--- /dev/null
+++ b/messagebus/src/tests/shutdown/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+shutdown_test
+messagebus_shutdown_test_app
diff --git a/messagebus/src/tests/shutdown/CMakeLists.txt b/messagebus/src/tests/shutdown/CMakeLists.txt
new file mode 100644
index 00000000000..69c849ae5fb
--- /dev/null
+++ b/messagebus/src/tests/shutdown/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_shutdown_test_app
+ SOURCES
+ shutdown.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_shutdown_test_app COMMAND messagebus_shutdown_test_app)
diff --git a/messagebus/src/tests/shutdown/DESC b/messagebus/src/tests/shutdown/DESC
new file mode 100644
index 00000000000..1f289ba9c23
--- /dev/null
+++ b/messagebus/src/tests/shutdown/DESC
@@ -0,0 +1 @@
+shutdown test. Take a look at shutdown.cpp for details.
diff --git a/messagebus/src/tests/shutdown/FILES b/messagebus/src/tests/shutdown/FILES
new file mode 100644
index 00000000000..ce150a62325
--- /dev/null
+++ b/messagebus/src/tests/shutdown/FILES
@@ -0,0 +1 @@
+shutdown.cpp
diff --git a/messagebus/src/tests/shutdown/shutdown.cpp b/messagebus/src/tests/shutdown/shutdown.cpp
new file mode 100644
index 00000000000..d4c5544d469
--- /dev/null
+++ b/messagebus/src/tests/shutdown/shutdown.cpp
@@ -0,0 +1,159 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("shutdown_test");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routing/errordirective.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+
+using namespace mbus;
+
+class Test : public vespalib::TestApp {
+private:
+ void requireThatListenFailedIsExceptionSafe();
+ void requireThatShutdownOnSourceWithPendingIsSafe();
+ void requireThatShutdownOnIntermediateWithPendingIsSafe();
+
+public:
+ int Main() {
+ TEST_INIT("shutdown_test");
+
+ requireThatListenFailedIsExceptionSafe(); TEST_FLUSH();
+ requireThatShutdownOnSourceWithPendingIsSafe(); TEST_FLUSH();
+ requireThatShutdownOnIntermediateWithPendingIsSafe(); TEST_FLUSH();
+
+ TEST_DONE();
+ }
+};
+
+static const double TIMEOUT = 120;
+
+TEST_APPHOOK(Test);
+
+void
+Test::requireThatListenFailedIsExceptionSafe()
+{
+ FRT_Supervisor orb;
+ ASSERT_TRUE(orb.Listen(0));
+ ASSERT_TRUE(orb.Start());
+
+ Slobrok slobrok;
+ try {
+ TestServer bar(MessageBusParams(),
+ RPCNetworkParams()
+ .setListenPort(orb.GetListenPort())
+ .setSlobrokConfig(slobrok.config()));
+ EXPECT_TRUE(false);
+ } catch (vespalib::Exception &e) {
+ EXPECT_EQUAL("Failed to start network.",
+ e.getMessage());
+ }
+ orb.ShutDown(true);
+}
+
+void
+Test::requireThatShutdownOnSourceWithPendingIsSafe()
+{
+ Slobrok slobrok;
+ TestServer dstServer(MessageBusParams()
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("dst"))
+ .setSlobrokConfig(slobrok.config()));
+ Receptor dstHandler;
+ DestinationSession::UP dstSession = dstServer.mb.createDestinationSession(
+ DestinationSessionParams()
+ .setName("session")
+ .setMessageHandler(dstHandler));
+ ASSERT_TRUE(dstSession.get() != NULL);
+
+ for (uint32_t i = 0; i < 10; ++i) {
+ Message::UP msg(new SimpleMessage("msg"));
+ {
+ TestServer srcServer(MessageBusParams()
+ .setRetryPolicy(IRetryPolicy::SP(new RetryTransientErrorsPolicy()))
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setSlobrokConfig(slobrok.config()));
+ Receptor srcHandler;
+ SourceSession::UP srcSession = srcServer.mb.createSourceSession(SourceSessionParams()
+ .setThrottlePolicy(IThrottlePolicy::SP())
+ .setReplyHandler(srcHandler));
+ ASSERT_TRUE(srcSession.get() != NULL);
+ ASSERT_TRUE(srcServer.waitSlobrok("dst/session", 1));
+ ASSERT_TRUE(srcSession->send(std::move(msg), "dst/session", true).isAccepted());
+ msg = dstHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ }
+ dstSession->acknowledge(std::move(msg));
+ }
+}
+
+void
+Test::requireThatShutdownOnIntermediateWithPendingIsSafe()
+{
+ Slobrok slobrok;
+ TestServer dstServer(MessageBusParams()
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("dst"))
+ .setSlobrokConfig(slobrok.config()));
+ Receptor dstHandler;
+ DestinationSession::UP dstSession = dstServer.mb.createDestinationSession(
+ DestinationSessionParams()
+ .setName("session")
+ .setMessageHandler(dstHandler));
+ ASSERT_TRUE(dstSession.get() != NULL);
+
+ TestServer srcServer(MessageBusParams()
+ .setRetryPolicy(IRetryPolicy::SP())
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setSlobrokConfig(slobrok.config()));
+ Receptor srcHandler;
+ SourceSession::UP srcSession = srcServer.mb.createSourceSession(SourceSessionParams()
+ .setThrottlePolicy(IThrottlePolicy::SP())
+ .setReplyHandler(srcHandler));
+ ASSERT_TRUE(srcSession.get() != NULL);
+ ASSERT_TRUE(srcServer.waitSlobrok("dst/session", 1));
+
+ for (uint32_t i = 0; i < 10; ++i) {
+ Message::UP msg(new SimpleMessage("msg"));
+ {
+ TestServer itrServer(MessageBusParams()
+ .setRetryPolicy(IRetryPolicy::SP(new RetryTransientErrorsPolicy()))
+ .addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("itr"))
+ .setSlobrokConfig(slobrok.config()));
+ Receptor itrHandler;
+ IntermediateSession::UP itrSession = itrServer.mb.createIntermediateSession(
+ IntermediateSessionParams()
+ .setName("session")
+ .setMessageHandler(itrHandler)
+ .setReplyHandler(itrHandler));
+ ASSERT_TRUE(itrSession.get() != NULL);
+ ASSERT_TRUE(srcServer.waitSlobrok("itr/session", 1));
+ ASSERT_TRUE(srcSession->send(std::move(msg), "itr/session dst/session", true).isAccepted());
+ msg = itrHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ itrSession->forward(std::move(msg));
+ msg = dstHandler.getMessage(TIMEOUT);
+ ASSERT_TRUE(msg.get() != NULL);
+ }
+ ASSERT_TRUE(srcServer.waitSlobrok("itr/session", 0));
+ dstSession->acknowledge(std::move(msg));
+ dstServer.mb.sync();
+ }
+}
diff --git a/messagebus/src/tests/simple-roundtrip/.gitignore b/messagebus/src/tests/simple-roundtrip/.gitignore
new file mode 100644
index 00000000000..c34bf4e6ca8
--- /dev/null
+++ b/messagebus/src/tests/simple-roundtrip/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+simple-roundtrip_test
+messagebus_simple-roundtrip_test_app
diff --git a/messagebus/src/tests/simple-roundtrip/CMakeLists.txt b/messagebus/src/tests/simple-roundtrip/CMakeLists.txt
new file mode 100644
index 00000000000..dff6ebf3e55
--- /dev/null
+++ b/messagebus/src/tests/simple-roundtrip/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_simple-roundtrip_test_app
+ SOURCES
+ simple-roundtrip.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_simple-roundtrip_test_app COMMAND messagebus_simple-roundtrip_test_app)
diff --git a/messagebus/src/tests/simple-roundtrip/DESC b/messagebus/src/tests/simple-roundtrip/DESC
new file mode 100644
index 00000000000..ad88203d593
--- /dev/null
+++ b/messagebus/src/tests/simple-roundtrip/DESC
@@ -0,0 +1 @@
+simple-roundtrip test. Take a look at simple-roundtrip.cpp for details.
diff --git a/messagebus/src/tests/simple-roundtrip/FILES b/messagebus/src/tests/simple-roundtrip/FILES
new file mode 100644
index 00000000000..c6a24435fe2
--- /dev/null
+++ b/messagebus/src/tests/simple-roundtrip/FILES
@@ -0,0 +1 @@
+simple-roundtrip.cpp
diff --git a/messagebus/src/tests/simple-roundtrip/simple-roundtrip.cpp b/messagebus/src/tests/simple-roundtrip/simple-roundtrip.cpp
new file mode 100644
index 00000000000..c59f072bd09
--- /dev/null
+++ b/messagebus/src/tests/simple-roundtrip/simple-roundtrip.cpp
@@ -0,0 +1,101 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("simple-roundtrip_test");
+
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+RoutingSpec getRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("pxy", "test/pxy/session"))
+ .addHop(HopSpec("dst", "test/dst/session"))
+ .addRoute(RouteSpec("test")
+ .addHop("pxy")
+ .addHop("dst")));
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("simple-roundtrip_test");
+
+ Slobrok slobrok;
+ TestServer srcNet(Identity("test/src"), getRouting(), slobrok);
+ TestServer pxyNet(Identity("test/pxy"), getRouting(), slobrok);
+ TestServer dstNet(Identity("test/dst"), getRouting(), slobrok);
+
+ Receptor src;
+ Receptor pxy;
+ Receptor dst;
+
+ SourceSession::UP ss = srcNet.mb.createSourceSession(src, SourceSessionParams());
+ IntermediateSession::UP is = pxyNet.mb.createIntermediateSession("session", true, pxy, pxy);
+ DestinationSession::UP ds = dstNet.mb.createDestinationSession("session", true, dst);
+
+ // wait for slobrok registration
+ ASSERT_TRUE(srcNet.waitSlobrok("test/pxy/session"));
+ ASSERT_TRUE(srcNet.waitSlobrok("test/dst/session"));
+ ASSERT_TRUE(pxyNet.waitSlobrok("test/dst/session"));
+
+ // send message on client
+ ss->send(SimpleMessage::UP(new SimpleMessage("test message")), "test");
+
+ // check message on proxy
+ Message::UP msg = pxy.getMessage();
+ ASSERT_TRUE(msg.get() != 0);
+ EXPECT_TRUE(msg->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(msg->getType() == SimpleProtocol::MESSAGE);
+ EXPECT_TRUE(static_cast<SimpleMessage&>(*msg).getValue() == "test message");
+
+ // forward message on proxy
+ static_cast<SimpleMessage&>(*msg).setValue("test message pxy");
+ is->forward(std::move(msg));
+
+ // check message on server
+ msg = dst.getMessage();
+ ASSERT_TRUE(msg.get() != 0);
+ EXPECT_TRUE(msg->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(msg->getType() == SimpleProtocol::MESSAGE);
+ EXPECT_TRUE(static_cast<SimpleMessage&>(*msg).getValue() == "test message pxy");
+
+ // send reply on server
+ SimpleReply::UP sr(new SimpleReply("test reply"));
+ msg->swapState(*sr);
+ ds->reply(Reply::UP(sr.release()));
+
+ // check reply on proxy
+ Reply::UP reply = pxy.getReply();
+ ASSERT_TRUE(reply.get() != 0);
+ EXPECT_TRUE(reply->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(reply->getType() == SimpleProtocol::REPLY);
+ EXPECT_TRUE(static_cast<SimpleReply&>(*reply).getValue() == "test reply");
+
+ // forward reply on proxy
+ static_cast<SimpleReply&>(*reply).setValue("test reply pxy");
+ is->forward(std::move(reply));
+
+ // check reply on client
+ reply = src.getReply();
+ ASSERT_TRUE(reply.get() != 0);
+ EXPECT_TRUE(reply->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(reply->getType() == SimpleProtocol::REPLY);
+ EXPECT_TRUE(static_cast<SimpleReply&>(*reply).getValue() == "test reply pxy");
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/simpleprotocol/.gitignore b/messagebus/src/tests/simpleprotocol/.gitignore
new file mode 100644
index 00000000000..8a096b651d4
--- /dev/null
+++ b/messagebus/src/tests/simpleprotocol/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+simpleprotocol_test
+messagebus_simpleprotocol_test_app
diff --git a/messagebus/src/tests/simpleprotocol/CMakeLists.txt b/messagebus/src/tests/simpleprotocol/CMakeLists.txt
new file mode 100644
index 00000000000..4b4e777ea57
--- /dev/null
+++ b/messagebus/src/tests/simpleprotocol/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_simpleprotocol_test_app
+ SOURCES
+ simpleprotocol.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_simpleprotocol_test_app COMMAND messagebus_simpleprotocol_test_app)
diff --git a/messagebus/src/tests/simpleprotocol/DESC b/messagebus/src/tests/simpleprotocol/DESC
new file mode 100644
index 00000000000..91d0fa36c57
--- /dev/null
+++ b/messagebus/src/tests/simpleprotocol/DESC
@@ -0,0 +1,3 @@
+Small test of the simple protocol defined in the test library. The
+protocol will be used to test other messagebus features, including
+cross-language compatibility.
diff --git a/messagebus/src/tests/simpleprotocol/FILES b/messagebus/src/tests/simpleprotocol/FILES
new file mode 100644
index 00000000000..f3c58f7d66e
--- /dev/null
+++ b/messagebus/src/tests/simpleprotocol/FILES
@@ -0,0 +1 @@
+simpleprotocol.cpp
diff --git a/messagebus/src/tests/simpleprotocol/simpleprotocol.cpp b/messagebus/src/tests/simpleprotocol/simpleprotocol.cpp
new file mode 100644
index 00000000000..eaf609a2be1
--- /dev/null
+++ b/messagebus/src/tests/simpleprotocol/simpleprotocol.cpp
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("simpleprotocol_test");
+
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/ireplyhandler.h>
+#include <vespa/messagebus/network/identity.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/messagebus/vtag.h>
+
+using namespace mbus;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("simpleprotocol_test");
+
+ vespalib::Version version = Vtag::currentVersion;
+ SimpleProtocol protocol;
+ EXPECT_TRUE(protocol.getName() == "Simple");
+
+ {
+ // test protocol
+ IRoutingPolicy::UP bogus = protocol.createPolicy("bogus", "");
+ EXPECT_TRUE(bogus.get() == 0);
+ }
+ TEST_FLUSH();
+ {
+ // test SimpleMessage
+ Message::UP msg(new SimpleMessage("test"));
+ EXPECT_TRUE(!msg->isReply());
+ EXPECT_TRUE(msg->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(msg->getType() == SimpleProtocol::MESSAGE);
+ EXPECT_TRUE(static_cast<SimpleMessage&>(*msg).getValue() == "test");
+ Blob b = protocol.encode(version, *msg);
+ EXPECT_TRUE(b.size() > 0);
+ Routable::UP tmp = protocol.decode(version, BlobRef(b));
+ ASSERT_TRUE(tmp.get() != 0);
+ EXPECT_TRUE(!tmp->isReply());
+ EXPECT_TRUE(tmp->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(tmp->getType() == SimpleProtocol::MESSAGE);
+ EXPECT_TRUE(static_cast<SimpleMessage&>(*tmp).getValue() == "test");
+ }
+ TEST_FLUSH();
+ {
+ // test SimpleReply
+ Reply::UP reply(new SimpleReply("reply"));
+ EXPECT_TRUE(reply->isReply());
+ EXPECT_TRUE(reply->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(reply->getType() == SimpleProtocol::REPLY);
+ EXPECT_TRUE(static_cast<SimpleReply&>(*reply).getValue() == "reply");
+ Blob b = protocol.encode(version, *reply);
+ EXPECT_TRUE(b.size() > 0);
+ Routable::UP tmp = protocol.decode(version, BlobRef(b));
+ ASSERT_TRUE(tmp.get() != 0);
+ EXPECT_TRUE(tmp->isReply());
+ EXPECT_TRUE(tmp->getProtocol() == SimpleProtocol::NAME);
+ EXPECT_TRUE(tmp->getType() == SimpleProtocol::REPLY);
+ EXPECT_TRUE(static_cast<SimpleReply&>(*tmp).getValue() == "reply");
+ }
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/slobrok/.gitignore b/messagebus/src/tests/slobrok/.gitignore
new file mode 100644
index 00000000000..6176a4876be
--- /dev/null
+++ b/messagebus/src/tests/slobrok/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+slobrok_test
+messagebus_slobrok_test_app
diff --git a/messagebus/src/tests/slobrok/CMakeLists.txt b/messagebus/src/tests/slobrok/CMakeLists.txt
new file mode 100644
index 00000000000..d21768c1f6b
--- /dev/null
+++ b/messagebus/src/tests/slobrok/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_slobrok_test_app
+ SOURCES
+ slobrok.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_slobrok_test_app COMMAND messagebus_slobrok_test_app)
diff --git a/messagebus/src/tests/slobrok/DESC b/messagebus/src/tests/slobrok/DESC
new file mode 100644
index 00000000000..7d68f120d91
--- /dev/null
+++ b/messagebus/src/tests/slobrok/DESC
@@ -0,0 +1,2 @@
+A simple test to ensure we are able to perform
+register/unregister/lookup of messagebus networks against the slobrok.
diff --git a/messagebus/src/tests/slobrok/FILES b/messagebus/src/tests/slobrok/FILES
new file mode 100644
index 00000000000..3fc79ffa0cb
--- /dev/null
+++ b/messagebus/src/tests/slobrok/FILES
@@ -0,0 +1 @@
+slobrok.cpp
diff --git a/messagebus/src/tests/slobrok/slobrok.cpp b/messagebus/src/tests/slobrok/slobrok.cpp
new file mode 100644
index 00000000000..d51a5330cd5
--- /dev/null
+++ b/messagebus/src/tests/slobrok/slobrok.cpp
@@ -0,0 +1,134 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("slobrok_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <string>
+#include <sstream>
+#include <vespa/slobrok/sbmirror.h>
+#include <vespa/messagebus/network/rpcnetwork.h>
+#include <vespa/vespalib/util/host_name.h>
+
+using slobrok::api::IMirrorAPI;
+
+using namespace mbus;
+
+string
+createSpec(int port)
+{
+ std::ostringstream str;
+ str << "tcp/";
+ str << vespalib::HostName::get();
+ str << ":";
+ str << port;
+ return str.str();
+}
+
+struct SpecList
+{
+ IMirrorAPI::SpecList _specList;
+ SpecList() : _specList() {}
+ SpecList(IMirrorAPI::SpecList input) : _specList(input) {}
+ SpecList &add(const string &name, const string &spec) {
+ _specList.push_back(std::make_pair(string(name),
+ string(spec)));
+ return *this;
+ }
+ void sort() {
+ std::sort(_specList.begin(), _specList.end());
+ }
+ bool operator==(SpecList &rhs) { // NB: MUTATE!
+ sort();
+ rhs.sort();
+ return _specList == rhs._specList;
+ }
+};
+
+bool
+compare(const IMirrorAPI &api, const string &pattern, SpecList expect)
+{
+ for (int i = 0; i < 250; ++i) {
+ SpecList actual(api.lookup(pattern));
+ if (actual == expect) {
+ return true;
+ }
+ FastOS_Thread::Sleep(100);
+ }
+ return false;
+}
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("slobrok_test");
+ Slobrok slobrok;
+ RPCNetwork net1(RPCNetworkParams()
+ .setIdentity(Identity("net/a"))
+ .setSlobrokConfig(slobrok.config()));
+ RPCNetwork net2(RPCNetworkParams()
+ .setIdentity(Identity("net/b"))
+ .setSlobrokConfig(slobrok.config()));
+ RPCNetwork net3(RPCNetworkParams()
+ .setIdentity(Identity("net/c"))
+ .setSlobrokConfig(slobrok.config()));
+ ASSERT_TRUE(net1.start());
+ ASSERT_TRUE(net2.start());
+ ASSERT_TRUE(net3.start());
+ string spec1 = createSpec(net1.getPort());
+ string spec2 = createSpec(net2.getPort());
+ string spec3 = createSpec(net3.getPort());
+
+ net1.registerSession("foo");
+ net2.registerSession("foo");
+ net2.registerSession("bar");
+ net3.registerSession("foo");
+ net3.registerSession("bar");
+ net3.registerSession("baz");
+
+ EXPECT_TRUE(compare(net1.getMirror(), "*/*/*", SpecList()
+ .add("net/a/foo", spec1)
+ .add("net/b/foo", spec2)
+ .add("net/b/bar", spec2)
+ .add("net/c/foo", spec3)
+ .add("net/c/bar", spec3)
+ .add("net/c/baz", spec3)));
+ EXPECT_TRUE(compare(net2.getMirror(), "*/*/*", SpecList()
+ .add("net/a/foo", spec1)
+ .add("net/b/foo", spec2)
+ .add("net/b/bar", spec2)
+ .add("net/c/foo", spec3)
+ .add("net/c/bar", spec3)
+ .add("net/c/baz", spec3)));
+ EXPECT_TRUE(compare(net3.getMirror(), "*/*/*", SpecList()
+ .add("net/a/foo", spec1)
+ .add("net/b/foo", spec2)
+ .add("net/b/bar", spec2)
+ .add("net/c/foo", spec3)
+ .add("net/c/bar", spec3)
+ .add("net/c/baz", spec3)));
+
+ net2.unregisterSession("bar");
+ net3.unregisterSession("bar");
+ net3.unregisterSession("baz");
+
+ EXPECT_TRUE(compare(net1.getMirror(), "*/*/*", SpecList()
+ .add("net/a/foo", spec1)
+ .add("net/b/foo", spec2)
+ .add("net/c/foo", spec3)));
+ EXPECT_TRUE(compare(net2.getMirror(), "*/*/*", SpecList()
+ .add("net/a/foo", spec1)
+ .add("net/b/foo", spec2)
+ .add("net/c/foo", spec3)));
+ EXPECT_TRUE(compare(net3.getMirror(), "*/*/*", SpecList()
+ .add("net/a/foo", spec1)
+ .add("net/b/foo", spec2)
+ .add("net/c/foo", spec3)));
+
+ net3.shutdown();
+ net2.shutdown();
+ net1.shutdown();
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/sourcesession/.gitignore b/messagebus/src/tests/sourcesession/.gitignore
new file mode 100644
index 00000000000..c6400268d94
--- /dev/null
+++ b/messagebus/src/tests/sourcesession/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+sourcesession_test
+messagebus_sourcesession_test_app
diff --git a/messagebus/src/tests/sourcesession/CMakeLists.txt b/messagebus/src/tests/sourcesession/CMakeLists.txt
new file mode 100644
index 00000000000..c2cf4f59682
--- /dev/null
+++ b/messagebus/src/tests/sourcesession/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_sourcesession_test_app
+ SOURCES
+ sourcesession.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_sourcesession_test_app COMMAND messagebus_sourcesession_test_app)
diff --git a/messagebus/src/tests/sourcesession/DESC b/messagebus/src/tests/sourcesession/DESC
new file mode 100644
index 00000000000..3417f6b602b
--- /dev/null
+++ b/messagebus/src/tests/sourcesession/DESC
@@ -0,0 +1,3 @@
+Simple test to verify the basic behavior of the resender and sequencer
+components in a full setup. This test is complemented by the resender
+and sequencer tests.
diff --git a/messagebus/src/tests/sourcesession/FILES b/messagebus/src/tests/sourcesession/FILES
new file mode 100644
index 00000000000..b7d8703e7f9
--- /dev/null
+++ b/messagebus/src/tests/sourcesession/FILES
@@ -0,0 +1 @@
+sourcesession.cpp
diff --git a/messagebus/src/tests/sourcesession/sourcesession.cpp b/messagebus/src/tests/sourcesession/sourcesession.cpp
new file mode 100644
index 00000000000..f70789d81af
--- /dev/null
+++ b/messagebus/src/tests/sourcesession/sourcesession.cpp
@@ -0,0 +1,339 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("sourcesession_test");
+
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+struct DelayedHandler : public IMessageHandler
+{
+ DestinationSession::UP session;
+ uint32_t delay;
+
+ DelayedHandler(MessageBus &mb, uint32_t d) : session(), delay(d) {
+ session = mb.createDestinationSession("session", true, *this);
+ }
+ ~DelayedHandler() {
+ session.reset();
+ }
+ virtual void handleMessage(Message::UP msg) {
+ // this will block the transport thread in the server messagebus,
+ // but that should be ok, as we only want to test the timing in the
+ // client messagebus...
+ FastOS_Thread::Sleep(delay);
+ session->acknowledge(std::move(msg));
+ }
+};
+
+RoutingSpec getRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("dst", "dst/session"))
+ .addRoute(RouteSpec("dst").addHop("dst")));
+}
+
+RoutingSpec getBadRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("dst", "dst/session"))
+ .addRoute(RouteSpec("dst").addHop("dst")));
+}
+
+bool waitQueueSize(RoutableQueue &queue, uint32_t size) {
+ for (uint32_t i = 0; i < 60000; ++i) {
+ if (queue.size() == size) {
+ return true;
+ }
+ FastOS_Thread::Sleep(1);
+ }
+ return false;
+}
+
+class Test : public vespalib::TestApp
+{
+public:
+ void testSequencing();
+ void testResendError();
+ void testResendConnDown();
+ void testIllegalRoute();
+ void testNoServices();
+ void testBlockingClose();
+ void testNonBlockingClose();
+ int Main();
+};
+
+void
+Test::testSequencing()
+{
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+ TestServer dst(Identity("dst"), getRouting(), slobrok);
+
+ RoutableQueue srcQ;
+ RoutableQueue dstQ;
+
+ SourceSessionParams params;
+ params.setThrottlePolicy(IThrottlePolicy::SP());
+
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ, params);
+ DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dstQ);
+
+ ASSERT_TRUE(src.waitSlobrok("dst/session"));
+
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("foo", true, 1)), "dst").isAccepted());
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("foo", true, 2)), "dst").isAccepted());
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("foo", true, 1)), "dst").isAccepted());
+ EXPECT_TRUE(waitQueueSize(dstQ, 2));
+ FastOS_Thread::Sleep(250);
+ EXPECT_TRUE(waitQueueSize(dstQ, 2));
+ EXPECT_TRUE(waitQueueSize(srcQ, 0));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ EXPECT_TRUE(waitQueueSize(srcQ, 2));
+ EXPECT_TRUE(waitQueueSize(dstQ, 1));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 3));
+ ASSERT_TRUE(waitQueueSize(dstQ, 0));
+}
+
+void
+Test::testResendError()
+{
+ Slobrok slobrok;
+ RetryTransientErrorsPolicy::SP retryPolicy(new RetryTransientErrorsPolicy());
+ retryPolicy->setBaseDelay(0);
+ TestServer src(MessageBusParams().addProtocol(IProtocol::SP(new SimpleProtocol())).setRetryPolicy(retryPolicy),
+ RPCNetworkParams().setSlobrokConfig(slobrok.config()));
+ src.mb.setupRouting(getRouting());
+ TestServer dst(Identity("dst"), getRouting(), slobrok);
+
+ RoutableQueue srcQ;
+ RoutableQueue dstQ;
+
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ);
+ DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dstQ);
+
+ ASSERT_TRUE(src.waitSlobrok("dst/session"));
+
+ {
+ Message::UP msg(new SimpleMessage("foo"));
+ msg->getTrace().setLevel(9);
+ EXPECT_TRUE(ss->send(std::move(msg), "dst").isAccepted());
+ }
+ EXPECT_TRUE(waitQueueSize(dstQ, 1));
+ {
+ Routable::UP r = dstQ.dequeue(0);
+ Reply::UP reply(new EmptyReply());
+ r->swapState(*reply);
+ reply->addError(Error(ErrorCode::FATAL_ERROR, "error"));
+ ds->reply(std::move(reply));
+ }
+ EXPECT_TRUE(waitQueueSize(srcQ, 1));
+ EXPECT_TRUE(waitQueueSize(dstQ, 0));
+
+ {
+ Message::UP msg(new SimpleMessage("foo"));
+ msg->getTrace().setLevel(9);
+ EXPECT_TRUE(ss->send(std::move(msg), "dst").isAccepted());
+ }
+ EXPECT_TRUE(waitQueueSize(dstQ, 1));
+ {
+ Routable::UP r = dstQ.dequeue(0);
+ Reply::UP reply(new EmptyReply());
+ r->swapState(*reply);
+ reply->addError(Error(ErrorCode::TRANSIENT_ERROR, "error"));
+ ds->reply(std::move(reply));
+ }
+ EXPECT_TRUE(waitQueueSize(dstQ, 1));
+ EXPECT_TRUE(waitQueueSize(srcQ, 1));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 2));
+ ASSERT_TRUE(waitQueueSize(dstQ, 0));
+ {
+ string trace1 = srcQ.dequeue(0)->getTrace().toString();
+ string trace2 = srcQ.dequeue(0)->getTrace().toString();
+ fprintf(stderr, "\nTRACE DUMP:\n%s\n\n", trace1.c_str());
+ fprintf(stderr, "\nTRACE DUMP:\n%s\n\n", trace2.c_str());
+ }
+}
+
+void
+Test::testResendConnDown()
+{
+ Slobrok slobrok;
+ RetryTransientErrorsPolicy::SP retryPolicy(new RetryTransientErrorsPolicy());
+ retryPolicy->setBaseDelay(0);
+ TestServer src(MessageBusParams().addProtocol(IProtocol::SP(new SimpleProtocol())).setRetryPolicy(retryPolicy),
+ RPCNetworkParams().setSlobrokConfig(slobrok.config()));
+ src.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addHop(HopSpec("dst", "dst2/session"))
+ .addHop(HopSpec("pxy", "[All]").addRecipient("dst"))
+ .addRoute(RouteSpec("dst").addHop("pxy"))));
+ RoutableQueue srcQ;
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ);
+
+ TestServer dst(Identity("dst"), RoutingSpec(), slobrok);
+ RoutableQueue dstQ;
+ DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dstQ);
+ ASSERT_TRUE(src.waitSlobrok("dst/session", 1));
+
+ {
+ TestServer dst2(Identity("dst2"), RoutingSpec(), slobrok);
+ RoutableQueue dst2Q;
+ DestinationSession::UP ds2 = dst2.mb.createDestinationSession("session", true, dst2Q);
+ ASSERT_TRUE(src.waitSlobrok("dst2/session", 1));
+
+ Message::UP msg(new SimpleMessage("foo"));
+ msg->getTrace().setLevel(9);
+ EXPECT_TRUE(ss->send(std::move(msg), "dst").isAccepted());
+ EXPECT_TRUE(waitQueueSize(dst2Q, 1));
+ Routable::UP obj = dst2Q.dequeue(0);
+ obj->discard();
+ src.mb.setupRouting(RoutingSpec().addTable(RoutingTableSpec(SimpleProtocol::NAME)
+ .addHop(HopSpec("dst", "dst/session"))));
+ } // dst2 goes down, resend with new config
+
+ ASSERT_TRUE(waitQueueSize(dstQ, 1)); // fails
+ ASSERT_TRUE(waitQueueSize(srcQ, 0));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 1));
+ ASSERT_TRUE(waitQueueSize(dstQ, 0));
+
+ string trace = srcQ.dequeue(0)->getTrace().toString();
+ fprintf(stderr, "\nTRACE DUMP:\n%s\n\n", trace.c_str());
+}
+
+void
+Test::testIllegalRoute()
+{
+ Slobrok slobrok;
+ TestServer src(MessageBusParams()
+ .addProtocol(IProtocol::SP(new SimpleProtocol()))
+ .setRetryPolicy(IRetryPolicy::SP()),
+ RPCNetworkParams()
+ .setSlobrokConfig(slobrok.config()));
+ src.mb.setupRouting(getRouting());
+
+ RoutableQueue srcQ;
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ, SourceSessionParams());
+ {
+ // no such hop
+ Message::UP msg(new SimpleMessage("foo"));
+ msg->getTrace().setLevel(9);
+ msg->setRoute(Route::parse("bogus"));
+ EXPECT_TRUE(ss->send(std::move(msg)).isAccepted());
+ }
+ ASSERT_TRUE(waitQueueSize(srcQ, 1));
+ {
+ while (srcQ.size() > 0) {
+ Routable::UP routable = srcQ.dequeue(0);
+ ASSERT_TRUE(routable->isReply());
+ Reply::UP r(static_cast<Reply*>(routable.release()));
+ EXPECT_EQUAL(1u, r->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::NO_ADDRESS_FOR_SERVICE, r->getError(0).getCode());
+ string trace = r->getTrace().toString();
+ fprintf(stderr, "\nTRACE DUMP:\n%s\n\n", trace.c_str());
+ }
+ }
+}
+
+void
+Test::testNoServices()
+{
+ Slobrok slobrok;
+ TestServer src(MessageBusParams()
+ .addProtocol(IProtocol::SP(new SimpleProtocol()))
+ .setRetryPolicy(IRetryPolicy::SP()),
+ RPCNetworkParams()
+ .setSlobrokConfig(slobrok.config()));
+ src.mb.setupRouting(getBadRouting());
+
+ RoutableQueue srcQ;
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ);
+ {
+ // no services for hop
+ Message::UP msg(new SimpleMessage("foo"));
+ msg->getTrace().setLevel(9);
+ EXPECT_TRUE(ss->send(std::move(msg), "dst").isAccepted());
+ }
+ ASSERT_TRUE(waitQueueSize(srcQ, 1));
+ {
+ while (srcQ.size() > 0) {
+ Routable::UP routable = srcQ.dequeue(0);
+ ASSERT_TRUE(routable->isReply());
+ Reply::UP r(static_cast<Reply*>(routable.release()));
+ EXPECT_TRUE(r->getNumErrors() == 1);
+ EXPECT_TRUE(r->getError(0).getCode() == ErrorCode::NO_ADDRESS_FOR_SERVICE);
+ string trace = r->getTrace().toString();
+ fprintf(stderr, "\nTRACE DUMP:\n%s\n\n", trace.c_str());
+ }
+ }
+}
+
+void
+Test::testBlockingClose()
+{
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+ TestServer dst(Identity("dst"), getRouting(), slobrok);
+
+ RoutableQueue srcQ;
+ DelayedHandler dstH(dst.mb, 1000);
+ ASSERT_TRUE(src.waitSlobrok("dst/session"));
+
+ SourceSessionParams params;
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ, params);
+
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("foo")), "dst").isAccepted());
+ ss->close();
+ srcQ.handleMessage(Message::UP(new SimpleMessage("bogus")));
+ Routable::UP routable = srcQ.dequeue(0);
+ EXPECT_TRUE(routable->isReply());
+}
+
+void
+Test::testNonBlockingClose()
+{
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+
+ RoutableQueue srcQ;
+
+ SourceSessionParams params;
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ, params);
+ ss->close(); // this should not hang
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("sourcesession_test");
+ testSequencing(); TEST_FLUSH();
+ testResendError(); TEST_FLUSH();
+ testResendConnDown(); TEST_FLUSH();
+ testIllegalRoute(); TEST_FLUSH();
+ testNoServices(); TEST_FLUSH();
+ testBlockingClose(); TEST_FLUSH();
+ testNonBlockingClose(); TEST_FLUSH();
+ TEST_DONE();
+}
+
+TEST_APPHOOK(Test);
diff --git a/messagebus/src/tests/targetpool/.gitignore b/messagebus/src/tests/targetpool/.gitignore
new file mode 100644
index 00000000000..d6736ff12f1
--- /dev/null
+++ b/messagebus/src/tests/targetpool/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+targetpool_test
+messagebus_targetpool_test_app
diff --git a/messagebus/src/tests/targetpool/CMakeLists.txt b/messagebus/src/tests/targetpool/CMakeLists.txt
new file mode 100644
index 00000000000..2fedf07d03d
--- /dev/null
+++ b/messagebus/src/tests/targetpool/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_targetpool_test_app
+ SOURCES
+ targetpool.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_targetpool_test_app COMMAND messagebus_targetpool_test_app)
diff --git a/messagebus/src/tests/targetpool/DESC b/messagebus/src/tests/targetpool/DESC
new file mode 100644
index 00000000000..8ba567d0efd
--- /dev/null
+++ b/messagebus/src/tests/targetpool/DESC
@@ -0,0 +1 @@
+targetpool test. Take a look at targetpool.cpp for details.
diff --git a/messagebus/src/tests/targetpool/FILES b/messagebus/src/tests/targetpool/FILES
new file mode 100644
index 00000000000..5fb34e2994b
--- /dev/null
+++ b/messagebus/src/tests/targetpool/FILES
@@ -0,0 +1 @@
+targetpool.cpp
diff --git a/messagebus/src/tests/targetpool/targetpool.cpp b/messagebus/src/tests/targetpool/targetpool.cpp
new file mode 100644
index 00000000000..0e63be19547
--- /dev/null
+++ b/messagebus/src/tests/targetpool/targetpool.cpp
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("targetpool_test");
+
+#include <vespa/messagebus/vtag.h>
+#include <vespa/messagebus/network/rpctargetpool.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace mbus;
+
+class PoolTimer : public ITimer {
+public:
+ uint64_t millis;
+
+ PoolTimer() : millis(0) {
+ // empty
+ }
+
+ uint64_t getMilliTime() const {
+ return millis;
+ }
+};
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("targetpool_test");
+
+ // Necessary setup to be able to resolve targets.
+ Slobrok slobrok;
+ TestServer srv1(Identity("srv1"), RoutingSpec(), slobrok);
+ RPCServiceAddress adr1("", srv1.mb.getConnectionSpec());
+ TestServer srv2(Identity("srv2"), RoutingSpec(), slobrok);
+ RPCServiceAddress adr2("", srv2.mb.getConnectionSpec());
+ TestServer srv3(Identity("srv3"), RoutingSpec(), slobrok);
+ RPCServiceAddress adr3("", srv3.mb.getConnectionSpec());
+
+ FRT_Supervisor orb(1024u, 1);
+ ASSERT_TRUE(orb.Start());
+ std::unique_ptr<PoolTimer> ptr(new PoolTimer());
+ PoolTimer &timer = *ptr;
+ RPCTargetPool pool(std::move(ptr), 0.666);
+
+ // Assert that all connections expire.
+ RPCTarget::SP target;
+ ASSERT_TRUE((target = pool.getTarget(orb, adr1)).get() != NULL); target.reset();
+ ASSERT_TRUE((target = pool.getTarget(orb, adr2)).get() != NULL); target.reset();
+ ASSERT_TRUE((target = pool.getTarget(orb, adr3)).get() != NULL); target.reset();
+ EXPECT_EQUAL(3u, pool.size());
+ for (uint32_t i = 0; i < 10; ++i) {
+ pool.flushTargets(false);
+ EXPECT_EQUAL(3u, pool.size());
+ }
+ timer.millis += 999;
+ pool.flushTargets(false);
+ EXPECT_EQUAL(0u, pool.size());
+
+ // Assert that only idle connections expire.
+ ASSERT_TRUE((target = pool.getTarget(orb, adr1)).get() != NULL); target.reset();
+ ASSERT_TRUE((target = pool.getTarget(orb, adr2)).get() != NULL); target.reset();
+ ASSERT_TRUE((target = pool.getTarget(orb, adr3)).get() != NULL); target.reset();
+ EXPECT_EQUAL(3u, pool.size());
+ timer.millis += 444;
+ pool.flushTargets(false);
+ EXPECT_EQUAL(3u, pool.size());
+ ASSERT_TRUE((target = pool.getTarget(orb, adr2)).get() != NULL); target.reset();
+ ASSERT_TRUE((target = pool.getTarget(orb, adr3)).get() != NULL); target.reset();
+ timer.millis += 444;
+ pool.flushTargets(false);
+ EXPECT_EQUAL(2u, pool.size());
+ ASSERT_TRUE((target = pool.getTarget(orb, adr3)).get() != NULL); target.reset();
+ timer.millis += 444;
+ pool.flushTargets(false);
+ EXPECT_EQUAL(1u, pool.size());
+ timer.millis += 444;
+ pool.flushTargets(false);
+ EXPECT_EQUAL(0u, pool.size());
+
+ // Assert that connections never expire while they are referenced.
+ ASSERT_TRUE((target = pool.getTarget(orb, adr1)).get() != NULL);
+ EXPECT_EQUAL(1u, pool.size());
+ for (int i = 0; i < 10; ++i) {
+ timer.millis += 999;
+ pool.flushTargets(false);
+ EXPECT_EQUAL(1u, pool.size());
+ }
+ target.reset();
+ timer.millis += 999;
+ pool.flushTargets(false);
+ EXPECT_EQUAL(0u, pool.size());
+
+ orb.ShutDown(true);
+
+ TEST_DONE();
+}
diff --git a/messagebus/src/tests/throttling/.gitignore b/messagebus/src/tests/throttling/.gitignore
new file mode 100644
index 00000000000..86ed5cc9f60
--- /dev/null
+++ b/messagebus/src/tests/throttling/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+throttling_test
+messagebus_throttling_test_app
diff --git a/messagebus/src/tests/throttling/CMakeLists.txt b/messagebus/src/tests/throttling/CMakeLists.txt
new file mode 100644
index 00000000000..28c27971e6f
--- /dev/null
+++ b/messagebus/src/tests/throttling/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_throttling_test_app
+ SOURCES
+ throttling.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_throttling_test_app COMMAND messagebus_throttling_test_app)
diff --git a/messagebus/src/tests/throttling/DESC b/messagebus/src/tests/throttling/DESC
new file mode 100644
index 00000000000..4e8dfc56357
--- /dev/null
+++ b/messagebus/src/tests/throttling/DESC
@@ -0,0 +1 @@
+throttling test. Take a look at throttling.cpp for details.
diff --git a/messagebus/src/tests/throttling/FILES b/messagebus/src/tests/throttling/FILES
new file mode 100644
index 00000000000..037f6cd99b9
--- /dev/null
+++ b/messagebus/src/tests/throttling/FILES
@@ -0,0 +1 @@
+throttling.cpp
diff --git a/messagebus/src/tests/throttling/throttling.cpp b/messagebus/src/tests/throttling/throttling.cpp
new file mode 100644
index 00000000000..6d543318559
--- /dev/null
+++ b/messagebus/src/tests/throttling/throttling.cpp
@@ -0,0 +1,362 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("throttling_test");
+
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/dynamicthrottlepolicy.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routablequeue.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/staticthrottlepolicy.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/testserver.h>
+
+using namespace mbus;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class DynamicTimer : public ITimer {
+public:
+ uint64_t _millis;
+
+ DynamicTimer() : _millis(0) {
+ // empty
+ }
+
+ uint64_t getMilliTime() const {
+ return _millis;
+ }
+};
+
+RoutingSpec getRouting()
+{
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("dst", "dst/session"))
+ .addRoute(RouteSpec("dst").addHop("dst")));
+}
+
+bool waitQueueSize(RoutableQueue &queue, uint32_t size)
+{
+ for (uint32_t i = 0; i < 10000; ++i) {
+ if (queue.size() == size) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ return false;
+}
+
+bool waitPending(SourceSession& session, uint32_t size)
+{
+ for (uint32_t i = 0; i < 60000; ++i) {
+ if (session.getPendingCount() == size) {
+ return true;
+ }
+ FastOS_Thread::Sleep(1);
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class Test : public vespalib::TestApp {
+private:
+ uint32_t getWindowSize(DynamicThrottlePolicy &policy, DynamicTimer &timer, uint32_t maxPending);
+
+protected:
+ void testMaxPendingCount();
+ void testMaxPendingSize();
+ void testMinOne();
+ void testDynamicWindowSize();
+ void testIdleTimePeriod();
+ void testMinWindowSize();
+ void testMaxWindowSize();
+
+public:
+ int Main();
+};
+
+int
+Test::Main()
+{
+ TEST_INIT("throttling_test");
+
+ testMaxPendingCount(); TEST_FLUSH();
+ testMaxPendingSize(); TEST_FLUSH();
+ testMinOne(); TEST_FLUSH();
+ testDynamicWindowSize(); TEST_FLUSH();
+ testIdleTimePeriod(); TEST_FLUSH();
+ testMinWindowSize(); TEST_FLUSH();
+ testMaxWindowSize(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+TEST_APPHOOK(Test);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test::testMaxPendingCount()
+{
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+ TestServer dst(Identity("dst"), getRouting(), slobrok);
+
+ RoutableQueue srcQ;
+ RoutableQueue dstQ;
+
+ SourceSessionParams params;
+ StaticThrottlePolicy::SP policy(new StaticThrottlePolicy());
+ policy->setMaxPendingCount(5);
+ policy->setMaxPendingSize(0); // unlimited
+ params.setThrottlePolicy(policy);
+
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ, params);
+ DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dstQ);
+
+ ASSERT_TRUE(src.waitSlobrok("dst/session"));
+
+ for (uint32_t i = 0; i < 5; ++i) {
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+ }
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+
+ EXPECT_TRUE(waitQueueSize(dstQ, 5));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 1));
+
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+
+ EXPECT_TRUE(waitQueueSize(dstQ, 5));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 3));
+
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+
+ EXPECT_TRUE(waitQueueSize(dstQ, 5));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 8));
+ ASSERT_TRUE(waitQueueSize(dstQ, 0));
+}
+
+void
+Test::testMaxPendingSize()
+{
+ ASSERT_TRUE(SimpleMessage("1234567890").getApproxSize() == 10);
+ ASSERT_TRUE(SimpleMessage("123456").getApproxSize() == 6);
+ ASSERT_TRUE(SimpleMessage("12345").getApproxSize() == 5);
+ ASSERT_TRUE(SimpleMessage("1").getApproxSize() == 1);
+ ASSERT_TRUE(SimpleMessage("").getApproxSize() == 0);
+
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+ TestServer dst(Identity("dst"), getRouting(), slobrok);
+
+ RoutableQueue srcQ;
+ RoutableQueue dstQ;
+
+ SourceSessionParams params;
+ StaticThrottlePolicy::SP policy(new StaticThrottlePolicy());
+ policy->setMaxPendingCount(0); // unlimited
+ policy->setMaxPendingSize(2);
+ params.setThrottlePolicy(policy);
+
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ, params);
+ DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dstQ);
+
+ ASSERT_TRUE(src.waitSlobrok("dst/session"));
+ EXPECT_EQUAL(1u, SimpleMessage("1").getApproxSize());
+ EXPECT_EQUAL(2u, SimpleMessage("12").getApproxSize());
+
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("1")), "dst").isAccepted());
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("12")), "dst").isAccepted());
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("1")), "dst").isAccepted());
+
+ EXPECT_TRUE(waitQueueSize(dstQ, 2));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 1));
+
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("1")), "dst").isAccepted());
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 2));
+
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("12")), "dst").isAccepted());
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("1")), "dst").isAccepted());
+ EXPECT_TRUE(waitQueueSize(dstQ, 1));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 3));
+}
+
+void
+Test::testMinOne()
+{
+ ASSERT_TRUE(SimpleMessage("1234567890").getApproxSize() == 10);
+ ASSERT_TRUE(SimpleMessage("").getApproxSize() == 0);
+
+ Slobrok slobrok;
+ TestServer src(Identity(""), getRouting(), slobrok);
+ TestServer dst(Identity("dst"), getRouting(), slobrok);
+
+ RoutableQueue srcQ;
+ RoutableQueue dstQ;
+
+ SourceSessionParams params;
+ StaticThrottlePolicy::SP policy(new StaticThrottlePolicy());
+ policy->setMaxPendingCount(0); // unlimited
+ policy->setMaxPendingSize(5);
+ params.setThrottlePolicy(policy);
+
+ SourceSession::UP ss = src.mb.createSourceSession(srcQ, params);
+ DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dstQ);
+
+ ASSERT_TRUE(src.waitSlobrok("dst/session"));
+
+ EXPECT_TRUE(ss->send(Message::UP(new SimpleMessage("1234567890")), "dst").isAccepted());
+ EXPECT_TRUE(!ss->send(Message::UP(new SimpleMessage("")), "dst").isAccepted());
+
+ EXPECT_TRUE(waitQueueSize(dstQ, 1));
+ ds->acknowledge(Message::UP((Message*)dstQ.dequeue(0).release()));
+ ASSERT_TRUE(waitQueueSize(srcQ, 1));
+ EXPECT_TRUE(waitQueueSize(dstQ, 0));
+}
+
+
+void
+Test::testDynamicWindowSize()
+{
+ std::unique_ptr<DynamicTimer> ptr(new DynamicTimer());
+ DynamicTimer *timer = ptr.get();
+ DynamicThrottlePolicy policy(std::move(ptr));
+
+ policy.setWindowSizeIncrement(5);
+
+ double windowSize = getWindowSize(policy, *timer, 100);
+ ASSERT_TRUE(windowSize >= 90 && windowSize <= 110);
+
+ windowSize = getWindowSize(policy, *timer, 200);
+ ASSERT_TRUE(windowSize >= 90 && windowSize <= 210);
+
+ windowSize = getWindowSize(policy, *timer, 50);
+ ASSERT_TRUE(windowSize >= 9 && windowSize <= 55);
+
+ windowSize = getWindowSize(policy, *timer, 500);
+ ASSERT_TRUE(windowSize >= 90 && windowSize <= 505);
+
+ windowSize = getWindowSize(policy, *timer, 100);
+ ASSERT_TRUE(windowSize >= 90 && windowSize <= 110);
+}
+
+void
+Test::testIdleTimePeriod()
+{
+ ITimer::UP ptr(new DynamicTimer());
+ DynamicTimer *timer = static_cast<DynamicTimer*>(ptr.get());
+ DynamicThrottlePolicy policy(std::move(ptr));
+
+ policy.setWindowSizeIncrement(5);
+
+ double windowSize = getWindowSize(policy, *timer, 100);
+ ASSERT_TRUE(windowSize >= 90 && windowSize <= 110);
+
+ SimpleMessage msg("foo");
+ timer->_millis += 30001;
+ ASSERT_TRUE(policy.canSend(msg, 0));
+ ASSERT_TRUE(windowSize >= 90 && windowSize <= 110);
+
+ timer->_millis += 60001;
+ ASSERT_TRUE(policy.canSend(msg, 50));
+ EXPECT_EQUAL(55u, policy.getMaxPendingCount());
+
+ timer->_millis += 60001;
+ ASSERT_TRUE(policy.canSend(msg, 0));
+ EXPECT_EQUAL(5u, policy.getMaxPendingCount());
+}
+
+void
+Test::testMinWindowSize()
+{
+ ITimer::UP ptr(new DynamicTimer());
+ DynamicTimer *timer = static_cast<DynamicTimer*>(ptr.get());
+ DynamicThrottlePolicy policy(std::move(ptr));
+
+ policy.setWindowSizeIncrement(5);
+ policy.setMinWindowSize(150);
+
+ double windowSize = getWindowSize(policy, *timer, 200);
+ ASSERT_TRUE(windowSize >= 150 && windowSize <= 210);
+}
+
+void
+Test::testMaxWindowSize()
+{
+ ITimer::UP ptr(new DynamicTimer());
+ DynamicTimer *timer = static_cast<DynamicTimer*>(ptr.get());
+ DynamicThrottlePolicy policy(std::move(ptr));
+
+ policy.setWindowSizeIncrement(5);
+ policy.setMaxWindowSize(50);
+
+ double windowSize = getWindowSize(policy, *timer, 100);
+ ASSERT_TRUE(windowSize >= 40 && windowSize <= 50);
+
+ policy.setMaxPendingCount(15);
+ windowSize = getWindowSize(policy, *timer, 100);
+ ASSERT_TRUE(windowSize >= 10 && windowSize <= 15);
+
+}
+
+uint32_t
+Test::getWindowSize(DynamicThrottlePolicy &policy, DynamicTimer &timer, uint32_t maxPending)
+{
+ SimpleMessage msg("foo");
+ SimpleReply reply("bar");
+
+ for (uint32_t i = 0; i < 999; ++i) {
+ uint32_t numPending = 0;
+ while (policy.canSend(msg, numPending)) {
+ policy.processMessage(msg);
+ ++numPending;
+ }
+
+ uint64_t tripTime = (numPending < maxPending) ? 1000 : 1000 + (numPending - maxPending) * 1000;
+ timer._millis += tripTime;
+
+ for( ; numPending > 0 ; --numPending) {
+ policy.processReply(reply);
+ }
+ }
+ uint32_t ret = policy.getMaxPendingCount();
+ printf("getWindowSize() = %d\n", ret);
+ return ret;
+}
diff --git a/messagebus/src/tests/timeout/.gitignore b/messagebus/src/tests/timeout/.gitignore
new file mode 100644
index 00000000000..c63e63d1685
--- /dev/null
+++ b/messagebus/src/tests/timeout/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+timeout_test
+messagebus_timeout_test_app
diff --git a/messagebus/src/tests/timeout/CMakeLists.txt b/messagebus/src/tests/timeout/CMakeLists.txt
new file mode 100644
index 00000000000..f50b81ff03f
--- /dev/null
+++ b/messagebus/src/tests/timeout/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_timeout_test_app
+ SOURCES
+ timeout.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_timeout_test_app COMMAND messagebus_timeout_test_app)
diff --git a/messagebus/src/tests/timeout/DESC b/messagebus/src/tests/timeout/DESC
new file mode 100644
index 00000000000..c90169db16e
--- /dev/null
+++ b/messagebus/src/tests/timeout/DESC
@@ -0,0 +1 @@
+timeout test. Take a look at timeout.cpp for details.
diff --git a/messagebus/src/tests/timeout/FILES b/messagebus/src/tests/timeout/FILES
new file mode 100644
index 00000000000..b36cdeb4ddf
--- /dev/null
+++ b/messagebus/src/tests/timeout/FILES
@@ -0,0 +1 @@
+timeout.cpp
diff --git a/messagebus/src/tests/timeout/timeout.cpp b/messagebus/src/tests/timeout/timeout.cpp
new file mode 100644
index 00000000000..8d6b1739776
--- /dev/null
+++ b/messagebus/src/tests/timeout/timeout.cpp
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("timeout_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/network/identity.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+
+using namespace mbus;
+
+class Test : public vespalib::TestApp {
+public:
+ int Main();
+ void testZeroTimeout();
+ void testMessageExpires();
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("timeout_test");
+
+ testZeroTimeout(); TEST_FLUSH();
+ testMessageExpires(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+void
+Test::testZeroTimeout()
+{
+ Slobrok slobrok;
+ TestServer srcServer(Identity("src"), RoutingSpec(), slobrok);
+ TestServer dstServer(Identity("dst"), RoutingSpec(), slobrok);
+
+ Receptor srcHandler;
+ SourceSession::UP srcSession = srcServer.mb.createSourceSession(srcHandler, SourceSessionParams().setTimeout(0));
+ Receptor dstHandler;
+ DestinationSession::UP dstSession = dstServer.mb.createDestinationSession("session", true, dstHandler);
+
+ ASSERT_TRUE(srcServer.waitSlobrok("dst/session", 1));
+ ASSERT_TRUE(srcSession->send(Message::UP(new SimpleMessage("msg")), "dst/session", true).isAccepted());
+
+ Reply::UP reply = srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::TIMEOUT, reply->getError(0).getCode());
+}
+
+void
+Test::testMessageExpires()
+{
+ Slobrok slobrok;
+ TestServer srcServer(Identity("src"), RoutingSpec(), slobrok);
+ TestServer dstServer(Identity("dst"), RoutingSpec(), slobrok);
+
+ Receptor srcHandler, dstHandler;
+ SourceSession::UP srcSession = srcServer.mb.createSourceSession(srcHandler, SourceSessionParams().setTimeout(1));
+ DestinationSession::UP dstSession = dstServer.mb.createDestinationSession("session", true, dstHandler);
+
+ ASSERT_TRUE(srcServer.waitSlobrok("dst/session", 1));
+ ASSERT_TRUE(srcSession->send(Message::UP(new SimpleMessage("msg")), "dst/session", true).isAccepted());
+
+ Reply::UP reply = srcHandler.getReply();
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)ErrorCode::TIMEOUT, reply->getError(0).getCode());
+
+ Message::UP msg = dstHandler.getMessage(1);
+ if (msg.get() != NULL) {
+ msg->discard();
+ }
+}
diff --git a/messagebus/src/tests/trace-roundtrip/.gitignore b/messagebus/src/tests/trace-roundtrip/.gitignore
new file mode 100644
index 00000000000..cd1669fd7a8
--- /dev/null
+++ b/messagebus/src/tests/trace-roundtrip/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+trace-roundtrip_test
+messagebus_trace-roundtrip_test_app
diff --git a/messagebus/src/tests/trace-roundtrip/CMakeLists.txt b/messagebus/src/tests/trace-roundtrip/CMakeLists.txt
new file mode 100644
index 00000000000..94ed14c8d99
--- /dev/null
+++ b/messagebus/src/tests/trace-roundtrip/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_trace-roundtrip_test_app
+ SOURCES
+ trace-roundtrip.cpp
+ DEPENDS
+ messagebus_messagebus-test
+ messagebus
+)
+vespa_add_test(NAME messagebus_trace-roundtrip_test_app COMMAND messagebus_trace-roundtrip_test_app)
diff --git a/messagebus/src/tests/trace-roundtrip/DESC b/messagebus/src/tests/trace-roundtrip/DESC
new file mode 100644
index 00000000000..eb0d3b38e63
--- /dev/null
+++ b/messagebus/src/tests/trace-roundtrip/DESC
@@ -0,0 +1 @@
+trace-roundtrip test. Take a look at trace-roundtrip.cpp for details.
diff --git a/messagebus/src/tests/trace-roundtrip/FILES b/messagebus/src/tests/trace-roundtrip/FILES
new file mode 100644
index 00000000000..13b3374345f
--- /dev/null
+++ b/messagebus/src/tests/trace-roundtrip/FILES
@@ -0,0 +1 @@
+trace-roundtrip.cpp
diff --git a/messagebus/src/tests/trace-roundtrip/trace-roundtrip.cpp b/messagebus/src/tests/trace-roundtrip/trace-roundtrip.cpp
new file mode 100644
index 00000000000..72158383807
--- /dev/null
+++ b/messagebus/src/tests/trace-roundtrip/trace-roundtrip.cpp
@@ -0,0 +1,127 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("simple-roundtrip_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+
+using namespace mbus;
+
+//-----------------------------------------------------------------------------
+
+class Proxy : public IMessageHandler,
+ public IReplyHandler
+{
+private:
+ IntermediateSession::UP _session;
+public:
+ Proxy(MessageBus &bus);
+ void handleMessage(Message::UP msg);
+ void handleReply(Reply::UP reply);
+};
+
+Proxy::Proxy(MessageBus &bus)
+ : _session(bus.createIntermediateSession("session", true, *this, *this))
+{
+}
+
+void
+Proxy::handleMessage(Message::UP msg) {
+ msg->getTrace().trace(1, "Proxy message", false);
+ _session->forward(std::move(msg));
+}
+
+void
+Proxy::handleReply(Reply::UP reply) {
+ reply->getTrace().trace(1, "Proxy reply", false);
+ _session->forward(std::move(reply));
+}
+
+//-----------------------------------------------------------------------------
+
+class Server : public IMessageHandler
+{
+private:
+ DestinationSession::UP _session;
+public:
+ Server(MessageBus &bus);
+ void handleMessage(Message::UP msg);
+};
+
+Server::Server(MessageBus &bus)
+ : _session(bus.createDestinationSession("session", true, *this))
+{
+}
+
+void
+Server::handleMessage(Message::UP msg) {
+ msg->getTrace().trace(1, "Server message", false);
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ reply->getTrace().trace(1, "Server reply", false);
+ _session->reply(std::move(reply));
+}
+
+//-----------------------------------------------------------------------------
+
+TEST_SETUP(Test);
+
+RoutingSpec getRouting() {
+ return RoutingSpec()
+ .addTable(RoutingTableSpec("Simple")
+ .addHop(HopSpec("pxy", "test/pxy/session"))
+ .addHop(HopSpec("dst", "test/dst/session"))
+ .addRoute(RouteSpec("test").addHop("pxy").addHop("dst")));
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("simple-roundtrip_test");
+
+ Slobrok slobrok;
+ TestServer srcNet(Identity("test/src"), getRouting(), slobrok);
+ TestServer pxyNet(Identity("test/pxy"), getRouting(), slobrok);
+ TestServer dstNet(Identity("test/dst"), getRouting(), slobrok);
+
+ Receptor src;
+ Proxy pxy(pxyNet.mb);
+ Server dst(dstNet.mb);
+
+ SourceSession::UP ss = srcNet.mb.createSourceSession(src, SourceSessionParams());
+
+ // wait for slobrok registration
+ ASSERT_TRUE(srcNet.waitSlobrok("test/pxy/session"));
+ ASSERT_TRUE(srcNet.waitSlobrok("test/dst/session"));
+ ASSERT_TRUE(pxyNet.waitSlobrok("test/dst/session"));
+
+ Message::UP msg(new SimpleMessage(""));
+ msg->getTrace().setLevel(1);
+ msg->getTrace().trace(1, "Client message", false);
+ ss->send(std::move(msg), "test");
+ Reply::UP reply = src.getReply();
+ reply->getTrace().trace(1, "Client reply", false);
+ EXPECT_TRUE(reply->getNumErrors() == 0);
+
+ TraceNode t = TraceNode()
+ .addChild("Client message")
+ .addChild("Proxy message")
+ .addChild("Server message")
+ .addChild("Server reply")
+ .addChild("Proxy reply")
+ .addChild("Client reply");
+ EXPECT_TRUE(reply->getTrace().getRoot().encode() == t.encode());
+ TEST_DONE();
+}
diff --git a/messagebus/src/vespa/messagebus/.gitignore b/messagebus/src/vespa/messagebus/.gitignore
new file mode 100644
index 00000000000..873d9af7094
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/.gitignore
@@ -0,0 +1,7 @@
+.depend
+Makefile
+config-messagebus.cpp
+config-messagebus.h
+messagebus.def
+printversion
+/libmessagebus.so.5.1
diff --git a/messagebus/src/vespa/messagebus/CMakeLists.txt b/messagebus/src/vespa/messagebus/CMakeLists.txt
new file mode 100644
index 00000000000..81b86d8e0dc
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(messagebus
+ SOURCES
+ blob.cpp
+ blobref.cpp
+ callstack.cpp
+ configagent.cpp
+ destinationsession.cpp
+ destinationsessionparams.cpp
+ dynamicthrottlepolicy.cpp
+ emptyreply.cpp
+ error.cpp
+ errorcode.cpp
+ intermediatesession.cpp
+ intermediatesessionparams.cpp
+ message.cpp
+ messagebus.cpp
+ messagebusparams.cpp
+ messenger.cpp
+ protocolrepository.cpp
+ protocolset.cpp
+ reply.cpp
+ replygate.cpp
+ result.cpp
+ routable.cpp
+ routablequeue.cpp
+ rpcmessagebus.cpp
+ sendproxy.cpp
+ sequencer.cpp
+ sourcesession.cpp
+ sourcesessionparams.cpp
+ staticthrottlepolicy.cpp
+ systemtimer.cpp
+ vtag.cpp
+ $<TARGET_OBJECTS:messagebus_routing>
+ $<TARGET_OBJECTS:messagebus_network>
+ INSTALL lib64
+ DEPENDS
+)
+vespa_generate_config(messagebus ../../main/config/messagebus.def)
+install(FILES ../../main/config/messagebus.def DESTINATION var/db/vespa/config_server/serverdb/classes)
diff --git a/messagebus/src/vespa/messagebus/blob.cpp b/messagebus/src/vespa/messagebus/blob.cpp
new file mode 100644
index 00000000000..ec5a8978b2e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/blob.cpp
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".blob");
+#include "blob.h"
+
+namespace mbus {
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/blob.h b/messagebus/src/vespa/messagebus/blob.h
new file mode 100644
index 00000000000..101ea989a92
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/blob.h
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/alloc.h>
+
+namespace mbus {
+
+/**
+ * This class encapsulates a blob that is owned by the object. Objects
+ * of this class have destructive copy. Use Blob objects when you want
+ * to transfer the ownership of a blob, like when it is returned by a
+ * method.
+ **/
+class Blob
+{
+public:
+ /**
+ * Create a blob that will contain uninitialized memory with the
+ * given size.
+ *
+ * @param s size of the data to be created
+ **/
+ Blob(uint32_t s) :
+ _payload(s),
+ _sz(s)
+ { }
+ Blob(Blob && rhs) :
+ _payload(std::move(rhs._payload)),
+ _sz(rhs._sz)
+ {
+ rhs._sz = 0;
+ }
+ Blob & operator = (Blob && rhs) {
+ swap(rhs);
+ return *this;
+ }
+
+ void swap(Blob & rhs) {
+ _payload.swap(rhs._payload);
+ std::swap(_sz, rhs._sz);
+ }
+
+ /**
+ * Obtain the data owned by this Blob
+ *
+ * @return data
+ **/
+ char *data() { return static_cast<char *>(_payload.get()); }
+
+ /**
+ * Obtain the data owned by this Blob
+ *
+ * @return data
+ **/
+ const char *data() const { return static_cast<const char *>(_payload.get()); }
+
+ vespalib::DefaultAlloc & payload() { return _payload; }
+ const vespalib::DefaultAlloc & payload() const { return _payload; }
+ size_t size() const { return _sz; }
+private:
+ vespalib::DefaultAlloc _payload;
+ size_t _sz;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/blobref.cpp b/messagebus/src/vespa/messagebus/blobref.cpp
new file mode 100644
index 00000000000..24109b38109
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/blobref.cpp
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".blobref");
+#include "blobref.h"
+
+namespace mbus {
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/blobref.h b/messagebus/src/vespa/messagebus/blobref.h
new file mode 100644
index 00000000000..1ade53749df
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/blobref.h
@@ -0,0 +1,55 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <stdint.h>
+#include "blob.h"
+
+namespace mbus {
+
+/**
+ * This class encapsulates a reference to a blob owned by someone
+ * else. This object can be copied freely, but does not own
+ * anything. This means that parameters of this class will typically
+ * only be valid during the invocation of the current method.
+ **/
+class BlobRef
+{
+private:
+ const char *_data; // default copy is ok
+ uint32_t _size;
+
+public:
+ /**
+ * Create a new BlobRef referring to the given memory.
+ *
+ * @param d the actual data
+ * @param s the size of the data in bytes
+ **/
+ BlobRef(const char *d, uint32_t s) : _data(d), _size(s) { }
+
+ /**
+ * Create a new BlobRef referring the memory owned by the given
+ * Blob.
+ *
+ * @param b blob owning the data we want a reference to
+ **/
+ BlobRef(const Blob &b) : _data(b.data()), _size(b.size()) { }
+
+ /**
+ * Obtain a pointer to the raw data referenced by this object.
+ *
+ * @return raw data pointer
+ **/
+ const char *data() const { return _data; }
+
+ /**
+ * Obtain the size of the data referenced by this object
+ *
+ * @return raw data size
+ **/
+ uint32_t size() const { return _size; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/callstack.cpp b/messagebus/src/vespa/messagebus/callstack.cpp
new file mode 100644
index 00000000000..87cff3088bf
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/callstack.cpp
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".callstack");
+#include "callstack.h"
+#include "message.h"
+#include "reply.h"
+#include "idiscardhandler.h"
+
+namespace mbus {
+
+void
+CallStack::discard()
+{
+ while (!_stack.empty()) {
+ const Frame &frame = _stack.back();
+ if (frame.discardHandler != NULL) {
+ frame.discardHandler->handleDiscard(frame.ctx);
+ }
+ _stack.pop_back();
+ }
+}
+
+void
+CallStack::swap(CallStack &dst)
+{
+ _stack.swap(dst._stack);
+}
+
+void
+CallStack::push(IReplyHandler &replyHandler, Context ctx,
+ IDiscardHandler *discardHandler)
+{
+ Frame frame;
+ frame.replyHandler = &replyHandler;
+ frame.discardHandler = discardHandler;
+ frame.ctx = ctx;
+ _stack.push_back(frame);
+}
+
+IReplyHandler &
+CallStack::pop(Reply &reply)
+{
+ LOG_ASSERT(!_stack.empty());
+ const Frame &frame = _stack.back();
+ IReplyHandler *handler = frame.replyHandler;
+ reply.setContext(frame.ctx);
+ _stack.pop_back();
+ return *handler;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/callstack.h b/messagebus/src/vespa/messagebus/callstack.h
new file mode 100644
index 00000000000..80e673ee550
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/callstack.h
@@ -0,0 +1,88 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vector>
+#include "context.h"
+
+namespace mbus {
+
+class IDiscardHandler;
+class IReplyHandler;
+class Reply;
+
+/**
+ * A CallStack is used to ensure that a Reply travels the inverse path
+ * of its Message. Each Routable has a CallStack used to track its
+ * path. Each stack frame contains a pointer to an IReplyHandler and a
+ * message Context for that handler. Note that a CallStack does not
+ * own any objects. Also note that the CallStack object will not be
+ * copied when copying a Routable, as it is not part of the object
+ * value. This class is intended for internal messagebus use only.
+ **/
+class CallStack : public boost::noncopyable
+{
+private:
+ struct Frame {
+ IReplyHandler *replyHandler;
+ IDiscardHandler *discardHandler;
+ Context ctx;
+ };
+
+ typedef std::vector<Frame> Stack;
+
+ Stack _stack;
+
+public:
+ /**
+ * Create a new empty CallStack.
+ **/
+ CallStack() : _stack() { }
+
+ /**
+ * Swap the content of this and the argument stack.
+ *
+ * @param dst The stack to swap content with.
+ **/
+ void swap(CallStack &dst);
+
+ /**
+ * Discard this CallStack. This method should only be used when you are
+ * certain that it is safe to just throw away the stack. It has similar
+ * effects to stopping a thread, you need to know where it is safe to do so.
+ **/
+ void discard();
+
+ /**
+ * Obtain the number of frames currently on this stack.
+ *
+ * @return stack size in frames
+ **/
+ uint32_t size() const { return _stack.size(); }
+
+ /**
+ * Push a frame on this stack. The discard handler is an optional handler,
+ * and may be null.
+ *
+ * @param replyHandler The handler for the correponding reply.
+ * @param ctx The context to store.
+ * @param discardHandler The handler for discarded messages.
+ **/
+ void push(IReplyHandler &replyHandler, Context ctx,
+ IDiscardHandler *discardHandler = NULL);
+
+ /**
+ * Pop a frame from this stack. The handler part of the frame will
+ * be returned and the context part will be set on the given
+ * Reply. Invoke this method on an empty stack and terrible things
+ * will happen.
+ *
+ * @return the next handler on the stack
+ * @param reply Reply that will receive the next context
+ **/
+ IReplyHandler &pop(Reply &reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/common.h b/messagebus/src/vespa/messagebus/common.h
new file mode 100644
index 00000000000..6b12c642a68
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/common.h
@@ -0,0 +1,13 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/vespalib/stllike/string.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+
+namespace mbus {
+
+// Decide the type of string used once
+typedef vespalib::string string;
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/configagent.cpp b/messagebus/src/vespa/messagebus/configagent.cpp
new file mode 100644
index 00000000000..34fb0849f43
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/configagent.cpp
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".configagent");
+#include <vespa/messagebus/routing/routingspec.h>
+#include "configagent.h"
+#include "iconfighandler.h"
+
+using namespace config;
+using namespace messagebus;
+
+namespace mbus {
+
+ConfigAgent::ConfigAgent(IConfigHandler & handler)
+ : _handler(handler)
+{ }
+
+void
+ConfigAgent::configure(std::unique_ptr<MessagebusConfig> config)
+{
+ const MessagebusConfig &cfg(*config);
+ RoutingSpec spec;
+ typedef MessagebusConfig CFG;
+ for (uint32_t t = 0; t < cfg.routingtable.size(); ++t) {
+ const CFG::Routingtable &table = cfg.routingtable[t];
+ RoutingTableSpec tableSpec(table.protocol);
+ for (uint32_t h = 0; h < table.hop.size(); ++h) {
+ const CFG::Routingtable::Hop &hop = table.hop[h];
+ HopSpec hopSpec(hop.name, hop.selector);
+ for (uint32_t i = 0; i < hop.recipient.size(); ++i) {
+ hopSpec.addRecipient(hop.recipient[i]);
+ }
+ hopSpec.setIgnoreResult(hop.ignoreresult);
+ tableSpec.addHop(hopSpec);
+ }
+ for (uint32_t r = 0; r < table.route.size(); ++r) {
+ const CFG::Routingtable::Route &route = table.route[r];
+ RouteSpec routeSpec(route.name);
+ for (uint32_t i = 0; i < route.hop.size(); ++i) {
+ routeSpec.addHop(route.hop[i]);
+ }
+ tableSpec.addRoute(routeSpec);
+ }
+ spec.addTable(tableSpec);
+ }
+ _handler.setupRouting(spec);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/configagent.h b/messagebus/src/vespa/messagebus/configagent.h
new file mode 100644
index 00000000000..82724667336
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/configagent.h
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include <vespa/messagebus/common.h>
+#include <vespa/config/helper/configfetcher.h>
+#include <vespa/messagebus/config-messagebus.h>
+
+namespace mbus {
+
+class IConfigHandler;
+
+/**
+ * A ConfigAgent will register with the config server and obtain
+ * config on behalf of a IConfigHandler.
+ **/
+class ConfigAgent : public config::IFetcherCallback<messagebus::MessagebusConfig>,
+ public vespalib::noncopyable
+{
+private:
+ IConfigHandler &_handler;
+
+public:
+ ConfigAgent(IConfigHandler & handler);
+
+ // Implements IFetcherCallback
+ void configure(std::unique_ptr<messagebus::MessagebusConfig> config);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/context.h b/messagebus/src/vespa/messagebus/context.h
new file mode 100644
index 00000000000..087d41f8b80
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/context.h
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string.h>
+#include <stdint.h>
+
+namespace mbus {
+
+/**
+ * A context is an application specific unit of information that can
+ * be attached to a routable. Specifically, messagebus will ensure
+ * that when a reply is obtained, it will have the same context as the
+ * original message.
+ **/
+struct Context
+{
+ /**
+ * This is a region of memory that can be interpreted as either an
+ * integer, a floating-point number or a pointer.
+ **/
+ union {
+ uint64_t UINT64;
+ double DOUBLE;
+ void *PTR;
+ } value;
+
+ /**
+ * Create a context that is set to 0, however you interpret it.
+ **/
+ Context() { memset(&value, 0, sizeof(value)); }
+
+ /**
+ * Create a contex from an integer.
+ *
+ * @param v the value
+ **/
+ Context(uint64_t v) { value.UINT64 = v; }
+
+ /**
+ * Create a context from a floating-point number
+ *
+ * @param v the value
+ **/
+ Context(double v) { value.DOUBLE = v; }
+
+ /**
+ * Create a context from a pointer
+ *
+ * @param pt the pointer
+ **/
+ Context(void *pt) { value.PTR = pt; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/create-class-cpp.sh b/messagebus/src/vespa/messagebus/create-class-cpp.sh
new file mode 100755
index 00000000000..f7c209427a8
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/create-class-cpp.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+class=$1
+guard=`echo $class | tr 'a-z' 'A-Z'`
+name=`echo $class | tr 'A-Z' 'a-z'`
+
+cat <<EOF
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/log.h>
+LOG_SETUP(".$name");
+#include <vespa/fastos/fastos.h>
+#include "$name.h"
+
+namespace mbus {
+
+$class::$class()
+{
+}
+
+$class::~$class()
+{
+}
+
+} // namespace mbus
+EOF
diff --git a/messagebus/src/vespa/messagebus/create-class-h.sh b/messagebus/src/vespa/messagebus/create-class-h.sh
new file mode 100755
index 00000000000..c4df87c4f8a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/create-class-h.sh
@@ -0,0 +1,26 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+#!/bin/sh
+
+class=$1
+guard=`echo $class | tr 'a-z' 'A-Z'`
+
+cat <<EOF
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+namespace mbus {
+
+class $class
+{
+private:
+ $class(const $class &);
+ $class &operator=(const $class &);
+public:
+ $class();
+ virtual ~$class();
+};
+
+} // namespace mbus
+
+EOF
diff --git a/messagebus/src/vespa/messagebus/create-interface.sh b/messagebus/src/vespa/messagebus/create-interface.sh
new file mode 100755
index 00000000000..e6f93b24355
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/create-interface.sh
@@ -0,0 +1,22 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+#!/bin/sh
+
+class=$1
+guard=`echo $class | tr 'a-z' 'A-Z'`
+
+cat <<EOF
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+namespace mbus {
+
+class $class
+{
+public:
+ virtual ~$class() {}
+};
+
+} // namespace mbus
+
+EOF
diff --git a/messagebus/src/vespa/messagebus/destinationsession.cpp b/messagebus/src/vespa/messagebus/destinationsession.cpp
new file mode 100644
index 00000000000..2cf074ae3e2
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/destinationsession.cpp
@@ -0,0 +1,60 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".destinationsession");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "destinationsession.h"
+#include "messagebus.h"
+#include "emptyreply.h"
+
+namespace mbus {
+
+DestinationSession::DestinationSession(MessageBus &mbus, const DestinationSessionParams &params) :
+ _mbus(mbus),
+ _name(params.getName()),
+ _msgHandler(params.getMessageHandler())
+{
+ // empty
+}
+
+DestinationSession::~DestinationSession()
+{
+ close();
+}
+
+void
+DestinationSession::close()
+{
+ _mbus.unregisterSession(_name);
+ _mbus.sync();
+}
+
+void
+DestinationSession::acknowledge(Message::UP msg)
+{
+ Reply::UP ack(new EmptyReply());
+ ack->swapState(*msg);
+ reply(std::move(ack));
+}
+
+void
+DestinationSession::reply(Reply::UP ret)
+{
+ IReplyHandler &handler = ret->getCallStack().pop(*ret);
+ handler.handleReply(std::move(ret));
+}
+
+void
+DestinationSession::handleMessage(Message::UP msg)
+{
+ _msgHandler.handleMessage(std::move(msg));
+}
+
+const string
+DestinationSession::getConnectionSpec() const
+{
+ return _mbus.getConnectionSpec() + "/" + _name;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/destinationsession.h b/messagebus/src/vespa/messagebus/destinationsession.h
new file mode 100644
index 00000000000..f381a5cd7b7
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/destinationsession.h
@@ -0,0 +1,104 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <memory>
+#include <string>
+#include "destinationsessionparams.h"
+#include "imessagehandler.h"
+#include "reply.h"
+
+namespace mbus {
+
+class MessageBus;
+
+/**
+ * A DestinationSession is used to receive Message objects and reply
+ * with Reply objects.
+ */
+class DestinationSession : public boost::noncopyable, public IMessageHandler {
+private:
+ friend class MessageBus;
+
+ MessageBus &_mbus;
+ string _name;
+ IMessageHandler &_msgHandler;
+
+ /**
+ * This constructor is package private since only MessageBus is supposed to
+ * instantiate it.
+ *
+ * @param mbus The message bus that created this instance.
+ * @param params The parameter object for this session.
+ */
+ DestinationSession(MessageBus &mbus, const DestinationSessionParams &params);
+
+public:
+ /**
+ * Convenience typedef for an auto pointer to a DestinationSession object.
+ */
+ typedef std::unique_ptr<DestinationSession> UP;
+
+ /**
+ * The destructor untangles from messagebus. After this method returns,
+ * messagebus will not invoke any handlers associated with this session.
+ */
+ virtual ~DestinationSession();
+
+ /**
+ * This method unregisters this session from message bus, effectively
+ * disabling any more messages from being delivered to the message
+ * handler. After unregistering, this method calls {@link
+ * com.yahoo.messagebus.MessageBus#sync()} as to ensure that there are no
+ * threads currently entangled in the handler.
+ *
+ * This method will deadlock if you call it from the message handler.
+ */
+ void close();
+
+ /**
+ * Convenience method used to acknowledge a Message. This method will create
+ * an EmptyReply object, transfer the state from the Message to it and
+ * invoke the reply method in this object.
+ *
+ * @param msg the Message you want to acknowledge
+ */
+ void acknowledge(Message::UP msg);
+
+ /**
+ * Send a Reply as a response to a Message. The Reply will be routed back to
+ * where the Message came from. For this to work, it is important that the
+ * messagebus state is transferred from the Message (you want to reply to)
+ * to the Reply (you want to reply with). This is done with the
+ * Routable::transferState method.
+ *
+ * @param reply the Reply
+ */
+ void reply(Reply::UP reply);
+
+ /**
+ * Handle a Message obtained from messagebus.
+ *
+ * @param message the Message
+ */
+ void handleMessage(Message::UP message);
+
+ /**
+ * Returns the message handler of this session.
+ *
+ * @return The message handler.
+ */
+ IMessageHandler &getMessageHandler() { return _msgHandler; }
+
+ /**
+ * Returns the connection spec string for this session. This returns a
+ * combination of the owning message bus' own spec string and the name of
+ * this session.
+ *
+ * @return The connection string.
+ */
+ const string getConnectionSpec() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/destinationsessionparams.cpp b/messagebus/src/vespa/messagebus/destinationsessionparams.cpp
new file mode 100644
index 00000000000..38e8e4c8a3e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/destinationsessionparams.cpp
@@ -0,0 +1,15 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include "destinationsessionparams.h"
+
+namespace mbus {
+
+DestinationSessionParams::DestinationSessionParams() :
+ _name("destination"),
+ _broadcastName(true),
+ _handler(NULL)
+{
+ // empty
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/destinationsessionparams.h b/messagebus/src/vespa/messagebus/destinationsessionparams.h
new file mode 100644
index 00000000000..4026cdbff91
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/destinationsessionparams.h
@@ -0,0 +1,77 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <string>
+#include "imessagehandler.h"
+
+namespace mbus {
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus#createDestinationSession(MessageHandler,
+ * DestinationSessionParams)}, all parameters are held by this class. This class has reasonable default values for each
+ * parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class DestinationSessionParams {
+private:
+ string _name;
+ bool _broadcastName;
+ IMessageHandler *_handler;
+
+public:
+ /**
+ * Constructs a new instance of this class with default values.
+ */
+ DestinationSessionParams();
+
+ /**
+ * Returns the name to register with message bus.
+ *
+ * @return The name.
+ */
+ const string &getName() const { return _name; }
+
+ /**
+ * Sets the name to register with message bus.
+ *
+ * @param name The name to set.
+ * @return This, to allow chaining.
+ */
+ DestinationSessionParams &setName(const string &name) { _name = name; return *this; }
+
+ /**
+ * Returns whether or not to broadcast the name of this session on the network.
+ *
+ * @return True to broadcast, false otherwise.
+ */
+ bool getBroadcastName() const { return _broadcastName; }
+
+ /**
+ * Sets whether or not to broadcast the name of this session on the network.
+ *
+ * @param broadcastName True to broadcast, false otherwise.
+ * @return This, to allow chaining.
+ */
+ DestinationSessionParams &setBroadcastName(bool broadcastName) { _broadcastName = broadcastName; return *this; }
+
+ /**
+ * Returns the handler to receive incoming messages. If you call this method without first assigning a
+ * message handler to this object, you wil de-ref null.
+ *
+ * @return The handler.
+ */
+ IMessageHandler &getMessageHandler() const { return *_handler; }
+
+ /**
+ * Sets the handler to receive incoming messages.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ DestinationSessionParams &setMessageHandler(IMessageHandler &handler) { _handler = &handler; return *this; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/dynamicthrottlepolicy.cpp b/messagebus/src/vespa/messagebus/dynamicthrottlepolicy.cpp
new file mode 100644
index 00000000000..523e9a31721
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/dynamicthrottlepolicy.cpp
@@ -0,0 +1,205 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".dynamicthrottlepolicy");
+
+#include <vespa/vespalib/util/atomic.h>
+#include "dynamicthrottlepolicy.h"
+#include "systemtimer.h"
+#include <math.h>
+#include <climits>
+
+namespace mbus {
+
+DynamicThrottlePolicy::DynamicThrottlePolicy() :
+ _timer(new SystemTimer()),
+ _numSent(0),
+ _numOk(0),
+ _resizeRate(3),
+ _resizeTime(_timer->getMilliTime()),
+ _timeOfLastMessage(_timer->getMilliTime()),
+ _idleTimePeriod(60000),
+ _efficiencyThreshold(1),
+ _windowSizeIncrement(20),
+ _windowSize(_windowSizeIncrement),
+ _maxWindowSize(INT_MAX),
+ _minWindowSize(_windowSizeIncrement),
+ _windowSizeBackOff(0.9),
+ _weight(1),
+ _localMaxThroughput(0)
+{
+ // empty
+}
+
+DynamicThrottlePolicy::DynamicThrottlePolicy(double windowSizeIncrement) :
+ _timer(new SystemTimer()),
+ _numSent(0),
+ _numOk(0),
+ _resizeRate(3),
+ _resizeTime(_timer->getMilliTime()),
+ _timeOfLastMessage(_timer->getMilliTime()),
+ _idleTimePeriod(60000),
+ _efficiencyThreshold(1),
+ _windowSizeIncrement(windowSizeIncrement),
+ _windowSize(_windowSizeIncrement),
+ _maxWindowSize(INT_MAX),
+ _minWindowSize(_windowSizeIncrement),
+ _windowSizeBackOff(0.9),
+ _weight(1),
+ _localMaxThroughput(0)
+{
+ // empty
+}
+
+DynamicThrottlePolicy::DynamicThrottlePolicy(ITimer::UP timer) :
+ _timer(std::move(timer)),
+ _numSent(0),
+ _numOk(0),
+ _resizeRate(3),
+ _resizeTime(_timer->getMilliTime()),
+ _timeOfLastMessage(_timer->getMilliTime()),
+ _idleTimePeriod(60000),
+ _efficiencyThreshold(1),
+ _windowSizeIncrement(20),
+ _windowSize(_windowSizeIncrement),
+ _maxWindowSize(INT_MAX),
+ _minWindowSize(_windowSizeIncrement),
+ _windowSizeBackOff(0.9),
+ _weight(1),
+ _localMaxThroughput(0)
+{
+ // empty
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setEfficiencyThreshold(double efficiencyThreshold)
+{
+ _efficiencyThreshold = efficiencyThreshold;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setWindowSizeIncrement(double windowSizeIncrement)
+{
+ _windowSizeIncrement = windowSizeIncrement;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setWindowSizeBackOff(double windowSizeBackOff)
+{
+ _windowSizeBackOff = windowSizeBackOff;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setResizeRate(uint32_t resizeRate)
+{
+ _resizeRate = resizeRate;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setWeight(double weight)
+{
+ _weight = weight;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setIdleTimePeriod(uint64_t period)
+{
+ _idleTimePeriod = period;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setMaxWindowSize(double max)
+{
+ _maxWindowSize = max;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setMinWindowSize(double min)
+{
+ _minWindowSize = min;
+ return *this;
+}
+
+DynamicThrottlePolicy &
+DynamicThrottlePolicy::setMaxPendingCount(uint32_t maxCount)
+{
+ StaticThrottlePolicy::setMaxPendingCount(maxCount);
+ _maxWindowSize = maxCount;
+ return *this;
+}
+
+bool
+DynamicThrottlePolicy::canSend(const Message &msg, uint32_t pendingCount)
+{
+ if (!StaticThrottlePolicy::canSend(msg, pendingCount)) {
+ return false;
+ }
+ uint64_t time = _timer->getMilliTime();
+ if (time - _timeOfLastMessage > _idleTimePeriod) {
+ _windowSize = std::min(_windowSize, (double) pendingCount + _windowSizeIncrement);
+ }
+ _timeOfLastMessage = time;
+ return pendingCount < _windowSize;
+}
+
+void
+DynamicThrottlePolicy::processMessage(Message &msg)
+{
+ StaticThrottlePolicy::processMessage(msg);
+ if (++_numSent < _windowSize * _resizeRate) {
+ return;
+ }
+
+ uint64_t time = _timer->getMilliTime();
+ double elapsed = time - _resizeTime;
+ _resizeTime = time;
+
+ double throughput = _numOk / elapsed;
+ _numSent = 0;
+ _numOk = 0;
+
+ if (throughput > _localMaxThroughput * 1.01) {
+ LOG(debug, "WindowSize = %.2f, Throughput = %f", _windowSize, throughput);
+ _localMaxThroughput = throughput;
+ _windowSize += _weight*_windowSizeIncrement;
+ } else {
+ // scale up/down throughput for comparing to window size
+ double period = 1;
+ while(throughput*period/_windowSize < 2) {
+ period *= 10;
+ }
+ while(throughput*period/_windowSize > 2) {
+ period *= 0.1;
+ }
+ double efficiency = throughput*period/_windowSize;
+ LOG(debug, "WindowSize = %.2f, Throughput = %f, Efficiency = %.2f, Elapsed = %.2f, Period = %.2f", _windowSize, throughput, efficiency, elapsed, period);
+
+ if (efficiency < _efficiencyThreshold) {
+ double newSize = std::min(throughput * period, _windowSize);
+ _windowSize = std::min(newSize * _windowSizeBackOff, _windowSize - 2 * _windowSizeIncrement);
+ _localMaxThroughput = 0;
+ } else {
+ _windowSize += _weight*_windowSizeIncrement;
+ }
+ }
+ _windowSize = std::max(_minWindowSize, _windowSize);
+ _windowSize = std::min(_maxWindowSize, _windowSize);
+}
+
+void
+DynamicThrottlePolicy::processReply(Reply &reply)
+{
+ StaticThrottlePolicy::processReply(reply);
+ if (!reply.hasErrors()) {
+ ++_numOk;
+ }
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/dynamicthrottlepolicy.h b/messagebus/src/vespa/messagebus/dynamicthrottlepolicy.h
new file mode 100644
index 00000000000..1e29d82cfc7
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/dynamicthrottlepolicy.h
@@ -0,0 +1,178 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "itimer.h"
+#include "staticthrottlepolicy.h"
+
+namespace mbus {
+
+/**
+ * This is an implementatin of the {@link ThrottlePolicy} that offers dynamic limits to the number of pending
+ * messages a {@link SourceSession} is allowed to have.
+ *
+ * <b>NOTE:</b> By context, "pending" is refering to the number of sent messages that have not been replied to
+ * yet.
+ */
+class DynamicThrottlePolicy: public StaticThrottlePolicy {
+public:
+private:
+ ITimer::UP _timer;
+ uint32_t _numSent;
+ uint32_t _numOk;
+ uint32_t _resizeRate;
+ uint64_t _resizeTime;
+ uint64_t _timeOfLastMessage;
+ uint64_t _idleTimePeriod;
+ double _efficiencyThreshold;
+ double _windowSizeIncrement;
+ double _windowSize;
+ double _maxWindowSize;
+ double _minWindowSize;
+ double _windowSizeBackOff;
+ double _weight;
+ double _localMaxThroughput;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<DynamicThrottlePolicy> UP;
+ typedef std::shared_ptr<DynamicThrottlePolicy> SP;
+
+ /**
+ * Constructs a new instance of this policy and sets the appropriate default values of member data.
+ */
+ DynamicThrottlePolicy();
+
+ /**
+ * Constructs a new instance of this policy and sets the appropriate default values of member data.
+ *
+ * @param windowSizeIncrement Initial value for window size increment. Also used
+ * to set initial values for current window size and minimum window size.
+ */
+ DynamicThrottlePolicy(double windowSizeIncrement);
+
+ /**
+ * Constructs a new instance of this class using the given clock to calculate efficiency.
+ *
+ * @param timer The timer to use.
+ */
+ DynamicThrottlePolicy(ITimer::UP timer);
+
+ /**
+ * Sets the lower efficiency threshold at which the algorithm should perform window size back
+ * off. Efficiency is the correlation between throughput and window size. The algorithm will increase the
+ * window size until efficiency drops below the efficiency of the local maxima times this value.
+ *
+ * @param efficiencyThreshold The limit to set.
+ * @return This, to allow chaining.
+ * @see #setWindowSizeBackOff(double)
+ */
+ DynamicThrottlePolicy &setEfficiencyThreshold(double efficiencyThreshold);
+
+ /**
+ * Sets the step size used when increasing window size.
+ *
+ * @param windowSizeIncrement The step size to set.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setWindowSizeIncrement(double windowSizeIncrement);
+
+ /**
+ * Sets the factor of window size to back off to when the algorithm determines that efficiency is not
+ * increasing. A value of 1 means that there is no back off from the local maxima, and means that the
+ * algorithm will fail to reduce window size to something lower than a previous maxima. This value is
+ * capped to the [0, 1] range.
+ *
+ * @param windowSizeBackOff The back off to set.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setWindowSizeBackOff(double windowSizeBackOff);
+
+ /**
+ * Sets the rate at which the window size is updated. The larger the value, the less responsive the
+ * resizing becomes. However, the smaller the value, the less accurate the measurements become.
+ *
+ * @param resizeRate The rate to set.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setResizeRate(uint32_t resizeRate);
+
+ /**
+ * Sets the weight for this client. The larger the value, the more resources
+ * will be allocated to this clients. Resources are shared between clients
+ * proportiannally to their weights.
+ *
+ * @param weight The weight to set.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setWeight(double weight);
+
+ /**
+ * Sets the idle time period for this client. If nothing is sent trhoughout
+ * this time period, the dynamic window will retract.
+ *
+ * @param period The time period to set.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setIdleTimePeriod(uint64_t period);
+
+ /**
+ * Sets the maximium number of pending operations allowed at any time, in
+ * order to avoid using too much resources.
+ *
+ * @param max The max to set.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setMaxWindowSize(double max);
+
+ /**
+ * Sets the maximum number of pending messages allowed.
+ *
+ * @param maxCount The max count.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setMaxPendingCount(uint32_t maxCount);
+
+ /**
+ * Get the maximum number of pending operations allowed at any time.
+ *
+ * @return The maximum number of operations.
+ */
+ double getMaxWindowSize() const { return _maxWindowSize; }
+
+ /**
+ * Sets the minimium number of pending operations allowed at any time, in
+ * order to keep a level of performance.
+ *
+ * @param min The min to set.
+ * @return This, to allow chaining.
+ */
+ DynamicThrottlePolicy &setMinWindowSize(double min);
+
+ /**
+ * Get the minimum number of pending operations allowed at any time.
+ *
+ * @return The minimum number of operations.
+ */
+ double getMinWindowSize() const { return _minWindowSize; }
+
+ /**
+ * Returns the maximum number of pending messages allowed.
+ *
+ * @return The max limit.
+ */
+ uint32_t getMaxPendingCount() const { return (uint32_t)_windowSize; }
+
+ // Implements IThrottlePolicy.
+ bool canSend(const Message &msg, uint32_t pendingCount);
+
+ // Implements IThrottlePolicy.
+ void processMessage(Message &msg);
+
+ // Implements IThrottlePolicy.
+ void processReply(Reply &reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/emptyreply.cpp b/messagebus/src/vespa/messagebus/emptyreply.cpp
new file mode 100644
index 00000000000..cf13c2bf857
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/emptyreply.cpp
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".emptyreply");
+
+#include "emptyreply.h"
+
+namespace {
+
+static mbus::string EmptyReplyProtocolName = "";
+
+} // namespace anon
+
+namespace mbus {
+
+EmptyReply::EmptyReply()
+{
+ // empty
+}
+
+const string &
+EmptyReply::getProtocol() const
+{
+ return EmptyReplyProtocolName;
+}
+
+uint32_t
+EmptyReply::getType() const
+{
+ return 0;
+}
+
+Blob
+EmptyReply::encode() const
+{
+ return Blob(0);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/emptyreply.h b/messagebus/src/vespa/messagebus/emptyreply.h
new file mode 100644
index 00000000000..53639e0fef0
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/emptyreply.h
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "reply.h"
+
+namespace mbus {
+
+/**
+ * A concrete reply that contains no protocol-specific data. This is needed to
+ * enable messagebus to reply to messages that result in an error. It may also
+ * be used by the application for ack type replies. Objects of this class will
+ * identify as type 0, which is reserved for this use. Also note that whenever a
+ * protocol-specific reply encodes to an empty blob it will be decoded to an
+ * EmptyReply at its network peer.
+ */
+class EmptyReply : public Reply {
+public:
+ /**
+ * Constructs a new instance of this class.
+ */
+ EmptyReply();
+
+ /**
+ * This method returns the empty string to signal that it does not belong to
+ * a protocol.
+ *
+ * @return ""
+ */
+ virtual const string & getProtocol() const;
+
+ /**
+ * This method returns the message type id reserved for empty replies: 0
+ *
+ * @return 0
+ */
+ virtual uint32_t getType() const;
+
+ /**
+ * Encodes this reply into an empty blob.
+ *
+ * @return empty blob
+ */
+ virtual Blob encode() const;
+
+ uint8_t priority() const { return 8; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/error.cpp b/messagebus/src/vespa/messagebus/error.cpp
new file mode 100644
index 00000000000..736cda5e16b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/error.cpp
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".error");
+#include "error.h"
+#include "errorcode.h"
+
+namespace mbus {
+
+Error::Error()
+ : _code(ErrorCode::NONE),
+ _msg(),
+ _service()
+{
+ // empty
+}
+
+Error::Error(uint32_t c, const string &m, const string &s)
+ : _code(c),
+ _msg(m),
+ _service(s)
+{
+ // empty
+}
+
+string
+Error::toString() const
+{
+ string name(ErrorCode::getName(_code));
+ if (name.empty()) {
+ name = vespalib::make_vespa_string("%u", _code);
+ }
+ return vespalib::make_vespa_string("[%s @ %s]: %s", name.c_str(),
+ _service.empty() ? "localhost" : _service.c_str(),
+ _msg.c_str());
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/error.h b/messagebus/src/vespa/messagebus/error.h
new file mode 100644
index 00000000000..f94e6532307
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/error.h
@@ -0,0 +1,71 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <stdint.h>
+#include <vespa/messagebus/common.h>
+
+#ifdef Error
+#undef Error
+#endif
+
+namespace mbus {
+
+/**
+ * An Error contains an error code (@ref ErrorCode) combined with an
+ * error message.
+ **/
+class Error
+{
+private:
+ uint32_t _code;
+ string _msg;
+ string _service;
+
+public:
+ /**
+ * Create an error with error code NONE and an empty message. This
+ * constructor is not intended for application use, but is needed
+ * for standard library containers.
+ **/
+ Error();
+
+ /**
+ * Create a new error with the given code and message
+ *
+ * @param c error code
+ * @param m error message
+ * @param s error service
+ **/
+ Error(uint32_t c, const string &m, const string &s = "");
+
+ /**
+ * Obtain the error code of this error.
+ *
+ * @return error code
+ **/
+ uint32_t getCode() const { return _code; }
+
+ /**
+ * Obtain the error message of this error.
+ *
+ * @return error message
+ **/
+ const string &getMessage() const { return _msg; }
+
+ /**
+ * Obtain the service string of this error.
+ *
+ * @return service string
+ **/
+ const string &getService() const { return _service; }
+
+ /**
+ * Obtain a string representation of this error.
+ *
+ * @return string representation
+ **/
+ string toString() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/errorcode.cpp b/messagebus/src/vespa/messagebus/errorcode.cpp
new file mode 100644
index 00000000000..21d3c91544f
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/errorcode.cpp
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".errorcode");
+#include "errorcode.h"
+#include <vespa/vespalib/stllike/asciistream.h>
+
+namespace mbus {
+
+ErrorCode::ErrorCode()
+{
+ // empty
+}
+
+string
+ErrorCode::getName(uint32_t errorCode)
+{
+ switch (errorCode) {
+ case APP_FATAL_ERROR : return "APP_FATAL_ERROR";
+ case APP_TRANSIENT_ERROR : return "APP_TRANSIENT_ERROR";
+ case CONNECTION_ERROR : return "CONNECTION_ERROR";
+ case DECODE_ERROR : return "DECODE_ERROR";
+ case ENCODE_ERROR : return "ENCODE_ERROR";
+ case FATAL_ERROR : return "FATAL_ERROR";
+ case HANDSHAKE_FAILED : return "HANDSHAKE_FAILED";
+ case ILLEGAL_ROUTE : return "ILLEGAL_ROUTE";
+ case INCOMPATIBLE_VERSION : return "INCOMPATIBLE_VERSION";
+ case NETWORK_ERROR : return "NETWORK_ERROR";
+ case NETWORK_SHUTDOWN : return "NETWORK_SHUTDOWN";
+ case NO_ADDRESS_FOR_SERVICE : return "NO_ADDRESS_FOR_SERVICE";
+ case NO_SERVICES_FOR_ROUTE : return "NO_SERVICES_FOR_ROUTE";
+ case NONE : return "NONE";
+ case POLICY_ERROR : return "POLICY_ERROR";
+ case SEND_ABORTED : return "SEND_ABORTED";
+ case SEND_QUEUE_CLOSED : return "SEND_QUEUE_CLOSED";
+ case SEND_QUEUE_FULL : return "SEND_QUEUE_FULL";
+ case SEQUENCE_ERROR : return "SEQUENCE_ERROR";
+ case SERVICE_OOS : return "SERVICE_OOS";
+ case SESSION_BUSY : return "SESSION_BUSY";
+ case TIMEOUT : return "TIMEOUT";
+ case TRANSIENT_ERROR : return "TRANSIENT_ERROR";
+ case UNKNOWN_POLICY : return "UNKNOWN_POLICY";
+ case UNKNOWN_PROTOCOL : return "UNKNOWN_PROTOCOL";
+ case UNKNOWN_SESSION : return "UNKNOWN_SESSION";
+ default : {
+ vespalib::asciistream os;
+ os << "UNKNOWN(" << errorCode << ')';
+ return os.str();
+ }
+ }
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/errorcode.h b/messagebus/src/vespa/messagebus/errorcode.h
new file mode 100644
index 00000000000..7667c75725e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/errorcode.h
@@ -0,0 +1,128 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <stdint.h>
+#include <vespa/messagebus/common.h>
+
+namespace mbus {
+
+/**
+ * This class contains the reserved error codes that are used by
+ * messagebus. It also defines the error code ranges that may be used
+ * by applications. Note that this class should never be instantiated.
+ * An error code is a number with some added semantics. Legal error
+ * codes are separated into 4 value ranges. An error can be either
+ * fatal or transient. Inside each category, the error can be either
+ * messagebus specific or application specific. A fatal error signals
+ * that something is wrong and that it will not help to resend the
+ * message. A transient error signals that it may help to resend the
+ * message at a later time.
+ * <pre>
+ * transient errors:
+ * messagebus: [100000, 150000>
+ * application: [150000, 200000>
+ * fatal errors:
+ * messagebus: [200000, 250000>
+ * application: [250000, 300000>
+ * </pre>
+ **/
+class ErrorCode {
+public:
+ enum {
+ // The code is here for completeness.
+ NONE = 0,
+
+ // A general transient error, resending is possible.
+ TRANSIENT_ERROR = 100000,
+
+ // Sending was rejected because throttler capacity is full.
+ SEND_QUEUE_FULL = TRANSIENT_ERROR + 1,
+
+ // No addresses found for the services of the message route.
+ NO_ADDRESS_FOR_SERVICE = TRANSIENT_ERROR + 2,
+
+ // A connection problem occured while sending.
+ CONNECTION_ERROR = TRANSIENT_ERROR + 3,
+
+ // The session specified for the message is unknown.
+ UNKNOWN_SESSION = TRANSIENT_ERROR + 4,
+
+ // The recipient session is busy.
+ SESSION_BUSY = TRANSIENT_ERROR + 5,
+
+ // Sending aborted by route verification.
+ SEND_ABORTED = TRANSIENT_ERROR + 6,
+
+ // Version handshake failed for any reason.
+ HANDSHAKE_FAILED = TRANSIENT_ERROR + 7,
+
+ // An application specific transient error.
+ APP_TRANSIENT_ERROR = TRANSIENT_ERROR + 50000,
+
+ // A general non-recoverable error, resending is not possible.
+ FATAL_ERROR = 200000,
+
+ // Sending was rejected because throttler is closed.
+ SEND_QUEUE_CLOSED = FATAL_ERROR + 1,
+
+ // The route of the message is illegal.
+ ILLEGAL_ROUTE = FATAL_ERROR + 2,
+
+ // No services found for the message route.
+ NO_SERVICES_FOR_ROUTE = FATAL_ERROR + 3,
+
+ // The selected service was out of service.
+ SERVICE_OOS = FATAL_ERROR + 4,
+
+ // An error occured while encoding the message.
+ ENCODE_ERROR = FATAL_ERROR + 5,
+
+ // A fatal network error occured while sending.
+ NETWORK_ERROR = FATAL_ERROR + 6,
+
+ // The protocol specified for the message is unknown.
+ UNKNOWN_PROTOCOL = FATAL_ERROR + 7,
+
+ // An error occured while decoding the message.
+ DECODE_ERROR = FATAL_ERROR + 8,
+
+ // A timeout occured while sending.
+ TIMEOUT = FATAL_ERROR + 9,
+
+ // The target is running an incompatible version.
+ INCOMPATIBLE_VERSION = FATAL_ERROR + 10,
+
+ // The policy specified in a route is unknown.
+ UNKNOWN_POLICY = FATAL_ERROR + 11,
+
+ // The network was shut down when attempting to send.
+ NETWORK_SHUTDOWN = FATAL_ERROR + 12,
+
+ // Exception thrown by routing policy.
+ POLICY_ERROR = FATAL_ERROR + 13,
+
+ // Exception thrown by routing policy.
+ SEQUENCE_ERROR = FATAL_ERROR + 14,
+
+ // An application specific non-recoverable error.
+ APP_FATAL_ERROR = FATAL_ERROR + 50000,
+
+ // No error codes are allowed to be this big.
+ ERROR_LIMIT = APP_FATAL_ERROR + 50000
+ };
+
+ /**
+ * Translates the given error code into its symbolic name.
+ *
+ * @param errorCode The error code to translate.
+ * @return The symbolic name.
+ **/
+ static string getName(uint32_t errorCode);
+
+private:
+ ErrorCode(); // hide
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/iconfighandler.h b/messagebus/src/vespa/messagebus/iconfighandler.h
new file mode 100644
index 00000000000..1a4dd4ab9ac
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/iconfighandler.h
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+namespace mbus {
+
+class RoutingSpec;
+
+/**
+ * This interface contains the method(s) used by the ConfigAgent to
+ * programmatically configure messagebus. It acts as insulation between
+ * the ConfigAgent and MessageBus to simplify testing of the config
+ * agent.
+ **/
+class IConfigHandler
+{
+public:
+ virtual ~IConfigHandler() {}
+
+ /**
+ * This method will be invoked to initialize or change the routing
+ * setup. The return value indicates whether the new setup was
+ * accepted or not. If false is returned the new routing was
+ * rejected and no change in the current setup have been done.
+ *
+ * @return true if new setup was accepted
+ * @param spec spec of new routing setup
+ **/
+ virtual bool setupRouting(const RoutingSpec &spec) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/idiscardhandler.h b/messagebus/src/vespa/messagebus/idiscardhandler.h
new file mode 100644
index 00000000000..4c55080328b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/idiscardhandler.h
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "context.h"
+
+namespace mbus {
+
+/**
+ * This interface is implemented by application components that require special
+ * handling when discarding a message with a non-empty callstack.
+ */
+class IDiscardHandler
+{
+public:
+ /**
+ * Virtual destructor required for inheritance.
+ */
+ virtual ~IDiscardHandler() { }
+
+ /**
+ * This method is invoked by message bus when a routable is being
+ * dicarded. This is invoked INSTEAD of the corresponding {@link
+ * ReplyHandler}.
+ *
+ * @param ctx The context of the discarded reply.
+ */
+ virtual void handleDiscard(Context ctx) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/imessagehandler.h b/messagebus/src/vespa/messagebus/imessagehandler.h
new file mode 100644
index 00000000000..d21936bb0bc
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/imessagehandler.h
@@ -0,0 +1,29 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include "message.h"
+
+namespace mbus {
+
+/**
+ * This interface is implemented by application components that want
+ * to handle incoming messages received from either an
+ * IntermediateSession or a DestinationSession.
+ **/
+class IMessageHandler
+{
+public:
+ virtual ~IMessageHandler() {}
+
+ /**
+ * This method is invoked by messagebus to deliver a Message.
+ *
+ * @param message the Message being delivered
+ **/
+ virtual void handleMessage(Message::UP message) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/intermediatesession.cpp b/messagebus/src/vespa/messagebus/intermediatesession.cpp
new file mode 100644
index 00000000000..88fd6069a81
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/intermediatesession.cpp
@@ -0,0 +1,80 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".intermediatesession");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "intermediatesession.h"
+#include "messagebus.h"
+#include "replygate.h"
+
+namespace mbus {
+
+IntermediateSession::IntermediateSession(MessageBus &mbus, const IntermediateSessionParams &params) :
+ _mbus(mbus),
+ _name(params.getName()),
+ _msgHandler(params.getMessageHandler()),
+ _replyHandler(params.getReplyHandler()),
+ _gate(new ReplyGate(_mbus))
+{
+ // empty
+}
+
+IntermediateSession::~IntermediateSession()
+{
+ _gate->close();
+ close();
+ _gate->subRef();
+}
+
+void
+IntermediateSession::close()
+{
+ _mbus.unregisterSession(_name);
+ _mbus.sync();
+}
+
+void
+IntermediateSession::forward(Routable::UP routable)
+{
+ if (routable->isReply()) {
+ forward(Reply::UP(static_cast<Reply*>(routable.release())));
+ } else {
+ forward(Message::UP(static_cast<Message*>(routable.release())));
+ }
+}
+
+void
+IntermediateSession::forward(Reply::UP reply)
+{
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+}
+
+void
+IntermediateSession::forward(Message::UP msg)
+{
+ msg->pushHandler(*this);
+ _gate->handleMessage(std::move(msg));
+}
+
+void
+IntermediateSession::handleMessage(Message::UP msg)
+{
+ _msgHandler.handleMessage(std::move(msg));
+}
+
+void
+IntermediateSession::handleReply(Reply::UP reply)
+{
+ _replyHandler.handleReply(std::move(reply));
+}
+
+const string
+IntermediateSession::getConnectionSpec() const
+{
+ return _mbus.getConnectionSpec() + "/" + _name;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/intermediatesession.h b/messagebus/src/vespa/messagebus/intermediatesession.h
new file mode 100644
index 00000000000..7f190c2ea4a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/intermediatesession.h
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <memory>
+#include <string>
+#include "reply.h"
+#include "imessagehandler.h"
+#include "intermediatesessionparams.h"
+
+namespace mbus {
+
+class MessageBus;
+class ReplyGate;
+
+/**
+ * An IntermediateSession is used to process Message and Reply objects
+ * on the way along a route.
+ **/
+class IntermediateSession : public boost::noncopyable,
+ public IMessageHandler,
+ public IReplyHandler
+{
+private:
+ friend class MessageBus;
+
+ MessageBus &_mbus;
+ string _name;
+ IMessageHandler &_msgHandler;
+ IReplyHandler &_replyHandler;
+ ReplyGate *_gate;
+
+ /**
+ * This constructor is declared package private since only MessageBus is supposed to instantiate it.
+ *
+ * @param mbus The message bus that created this instance.
+ * @param params The parameter object for this session.
+ */
+ IntermediateSession(MessageBus &mbus, const IntermediateSessionParams &params);
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<IntermediateSession> UP;
+
+ /**
+ * The destructor untangles from messagebus. After this method returns, messagebus will not invoke any
+ * handlers associated with this session.
+ */
+ virtual ~IntermediateSession();
+
+ /**
+ * This method unregisters this session from message bus, effectively disabling any more messages from
+ * being delivered to the message handler. After unregistering, this method calls {@link
+ * com.yahoo.messagebus.MessageBus#sync()} as to ensure that there are no threads currently entangled in
+ * the handler.
+ *
+ * This method will deadlock if you call it from the message or reply handler.
+ */
+ void close();
+
+ /**
+ * Forwards a routable to the next hop in its route. This method will never block.
+ *
+ * @param routable The routable to forward.
+ */
+ void forward(Routable::UP routable);
+
+ /**
+ * Convenience method to call {@link #forward(Routable)}.
+ *
+ * @param msg The message to forward.
+ */
+ void forward(Message::UP msg);
+
+ /**
+ * Convenience method to call {@link #forward(Routable)}.
+ *
+ * @param reply The reply to forward.
+ */
+ void forward(Reply::UP reply);
+
+ /**
+ * Returns the connection spec string for this session. This returns a combination of the owning message
+ * bus' own spec string and the name of this session.
+ *
+ * @return The connection string.
+ */
+ const string getConnectionSpec() const;
+
+ // Implements IMessageHandler.
+ void handleMessage(Message::UP message);
+
+ // Implements IReplyHandler.
+ void handleReply(Reply::UP reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/intermediatesessionparams.cpp b/messagebus/src/vespa/messagebus/intermediatesessionparams.cpp
new file mode 100644
index 00000000000..2d8944b8f26
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/intermediatesessionparams.cpp
@@ -0,0 +1,16 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include "intermediatesessionparams.h"
+
+namespace mbus {
+
+IntermediateSessionParams::IntermediateSessionParams() :
+ _name("intermediate"),
+ _broadcastName(true),
+ _msgHandler(NULL),
+ _replyHandler(NULL)
+{
+ // empty
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/intermediatesessionparams.h b/messagebus/src/vespa/messagebus/intermediatesessionparams.h
new file mode 100644
index 00000000000..84224b8803b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/intermediatesessionparams.h
@@ -0,0 +1,95 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <string>
+#include "imessagehandler.h"
+#include "ireplyhandler.h"
+
+namespace mbus {
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus#createIntermediateSession(MessageHandler,
+ * ReplyHandler, IntermediateSessionParams)}, all parameters are held by this class. This class has reasonable default
+ * values for each parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class IntermediateSessionParams {
+private:
+ string _name;
+ bool _broadcastName;
+ IMessageHandler *_msgHandler;
+ IReplyHandler *_replyHandler;
+
+public:
+ /**
+ * Constructs a new instance of this class with default values.
+ */
+ IntermediateSessionParams();
+
+ /**
+ * Returns the name to register with message bus.
+ *
+ * @return The name.
+ */
+ const string &getName() const { return _name; }
+
+ /**
+ * Sets the name to register with message bus.
+ *
+ * @param name The name to set.
+ * @return This, to allow chaining.
+ */
+ IntermediateSessionParams &setName(const string &name) { _name = name; return *this; }
+
+ /**
+ * Returns whether or not to broadcast the name of this session on the network.
+ *
+ * @return True to broadcast, false otherwise.
+ */
+ bool getBroadcastName() const { return _broadcastName; }
+
+ /**
+ * Sets whether or not to broadcast the name of this session on the network.
+ *
+ * @param broadcastName True to broadcast, false otherwise.
+ * @return This, to allow chaining.
+ */
+ IntermediateSessionParams &setBroadcastName(bool broadcastName) { _broadcastName = broadcastName; return *this; }
+
+ /**
+ * Returns the handler to receive incoming replies. If you call this method without first assigning a
+ * reply handler to this object, you wil de-ref null.
+ *
+ * @return The handler.
+ */
+ IReplyHandler &getReplyHandler() const { return *_replyHandler; }
+
+ /**
+ * Sets the handler to receive incoming replies.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ IntermediateSessionParams &setReplyHandler(IReplyHandler &handler) { _replyHandler = &handler; return *this; }
+
+ /**
+ * Returns the handler to receive incoming messages. If you call this method without first assigning a
+ * message handler to this object, you wil de-ref null.
+ *
+ * @return The handler.
+ */
+ IMessageHandler &getMessageHandler() const { return *_msgHandler; }
+
+ /**
+ * Sets the handler to receive incoming messages.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ IntermediateSessionParams &setMessageHandler(IMessageHandler &handler) { _msgHandler = &handler; return *this; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/iprotocol.h b/messagebus/src/vespa/messagebus/iprotocol.h
new file mode 100644
index 00000000000..c0af967f33b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/iprotocol.h
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/messagebus/routing/iroutingpolicy.h>
+#include <string>
+#include <vespa/vespalib/component/version.h>
+#include "blobref.h"
+#include "routable.h"
+
+namespace mbus {
+
+/**
+ * A protocol has support for decoding raw data into routable objects and for
+ * instantiating routing policy objects. Each protocol has a name. The name of
+ * a protocol is global across implementations. Protocols with the same name
+ * are expected to know how to encode and decode the same set of routables and
+ * also have support for the same set of routing policies.
+ */
+class IProtocol {
+public:
+ virtual ~IProtocol() {}
+
+ /**
+ * Convenience typedef for an auto pointer to an IProtocol object.
+ */
+ typedef std::unique_ptr<IProtocol> UP;
+
+ /**
+ * Convenience typedef for a shared pointer to a IProtocol object.
+ */
+ typedef std::shared_ptr<IProtocol> SP;
+
+ /**
+ * Obtain the name of this protocol.
+ *
+ * @return Protocol name.
+ */
+ virtual const string & getName() const = 0;
+
+ /**
+ * Instantiate a routing policy based on its name and parameter. Routing
+ * policies are created my messagebus based on the selector string. A
+ * selector path element using a custom routing policy is on the form
+ * '[name:param]'. The semantics of the parameter is up to the routing
+ * policy. It could be a simple value or even a config id.
+ *
+ * @param name Routing policy name (local to this protocol).
+ * @param param Ppolicy specific parameter.
+ * @return A newly created routing policy.
+ */
+ virtual IRoutingPolicy::UP createPolicy(const string &name,
+ const string &param) const = 0;
+
+ /**
+ * Encodes the protocol specific data of a routable into a byte array.
+ *
+ * Errors should be catched and logged by the encode implementation and
+ * an empty blob should be returned. This will make messagebus generate
+ * a reply to send back to the client.
+ *
+ * @param version The version to encode for.
+ * @param routable The routable to encode.
+ * @return The encoded data.
+ */
+ virtual Blob encode(const vespalib::Version &version, const Routable &routable) const = 0; // throw()
+
+ /**
+ * Decodes the protocol specific data into a routable of the correct type.
+ *
+ * Errors should be catched and logged by the decode implementation, and
+ * a null pointer should be returned. This will make messagebus generate
+ * a reply to send back to the client.
+ *
+ * @param version The version of the serialized routable.
+ * @param payload The payload to decode from.
+ * @return The decoded routable.
+ */
+ virtual Routable::UP decode(const vespalib::Version &version, BlobRef data) const = 0; // throw()
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/ireplyhandler.h b/messagebus/src/vespa/messagebus/ireplyhandler.h
new file mode 100644
index 00000000000..c30ca63c519
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/ireplyhandler.h
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include "reply.h"
+
+namespace mbus {
+
+/**
+ * This interface is implemented by application components that want
+ * to handle incoming replies received from either an
+ * IntermediateSession or a SourceSession.
+ **/
+class IReplyHandler
+{
+public:
+ virtual ~IReplyHandler() {}
+
+ /**
+ * This method is invoked by messagebus to deliver a Reply.
+ *
+ * @param reply the Reply being delivered
+ **/
+ virtual void handleReply(Reply::UP reply) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/ithrottlepolicy.h b/messagebus/src/vespa/messagebus/ithrottlepolicy.h
new file mode 100644
index 00000000000..1ac6d2c28df
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/ithrottlepolicy.h
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "reply.h"
+
+namespace mbus {
+
+/**
+ * An implementation of this interface is used by {@link SourceSession} to throttle output. Every message
+ * entering {@link SourceSession#send(Message)} needs to be accepted by this interface's {@link
+ * #canSend(Message, int)} method. All messages accepted are passed through the {@link
+ * #processMessage(Message)} method, and the corresponding replies are passed through the {@link
+ * #processReply(Reply)} method.
+ */
+class IThrottlePolicy {
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<IThrottlePolicy> UP;
+ typedef std::shared_ptr<IThrottlePolicy> SP;
+
+ /**
+ * Virtual destructor required for inheritance.
+ */
+ virtual ~IThrottlePolicy() { /* empty */ }
+
+ /**
+ * Returns whether or not the given message can be sent according to the current state of this policy.
+ *
+ * @param msg The message to evaluate.
+ * @param pendingCount The current number of pending messages.
+ * @return True to send the message.
+ */
+ virtual bool canSend(const Message &msg, uint32_t pendingCount) = 0;
+
+ /**
+ * This method is called once for every message that was accepted by {@link #canSend(Message, int)} and sent.
+ *
+ * @param msg The message beint sent.
+ */
+ virtual void processMessage(Message &msg) = 0;
+
+ /**
+ * This method is called once for every reply that is received.
+ *
+ * @param reply The reply received.
+ */
+ virtual void processReply(Reply &reply) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/itimer.h b/messagebus/src/vespa/messagebus/itimer.h
new file mode 100644
index 00000000000..6d03a0c45a2
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/itimer.h
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+
+namespace mbus {
+
+/**
+ * This interface wraps access to some timer that can be used to measure elapsed
+ * time, in milliseconds. This abstraction allows for unit testing the behavior
+ * of time-based constructs.
+ */
+class ITimer {
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<ITimer> UP;
+
+ /**
+ * Virtual destructor required for inheritance.
+ */
+ virtual ~ITimer() { /* empty */ }
+
+ /**
+ * Returns the current value of some arbitrary timer, in milliseconds. This
+ * method can only be used to measure elapsed time and is not related to any
+ * other notion of system or wall-clock time.
+ *
+ * @return The current value of the timer, in milliseconds.
+ */
+ virtual uint64_t getMilliTime() const = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/message.cpp b/messagebus/src/vespa/messagebus/message.cpp
new file mode 100644
index 00000000000..cdf290f2be1
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/message.cpp
@@ -0,0 +1,77 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".message");
+
+#include <vespa/vespalib/util/backtrace.h>
+#include "message.h"
+#include "reply.h"
+#include "ireplyhandler.h"
+#include "emptyreply.h"
+#include "error.h"
+#include "errorcode.h"
+
+namespace mbus {
+
+Message::Message() :
+ _route(),
+ _timeReceived(),
+ _timeRemaining(0),
+ _retryEnabled(true),
+ _retry(0)
+{
+ // empty
+}
+
+Message::~Message()
+{
+ if (getCallStack().size() > 0) {
+ string backtrace = vespalib::getStackTrace(0);
+ LOG(warning, "Deleted message %p with non-empty call-stack. Deleted at:\n%s",
+ this, backtrace.c_str());
+ Reply::UP reply(new EmptyReply());
+ swapState(*reply);
+ reply->addError(Error(ErrorCode::TRANSIENT_ERROR,
+ "The message object was deleted while containing state information; "
+ "generating an auto-reply."));
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+ }
+}
+
+void
+Message::swapState(Routable &rhs)
+{
+ Routable::swapState(rhs);
+ if (!rhs.isReply()) {
+ Message &msg = static_cast<Message&>(rhs);
+
+ std::swap(_route, msg._route);
+ std::swap(_retryEnabled, msg._retryEnabled);
+ std::swap(_retry, msg._retry);
+ std::swap(_timeReceived, msg._timeReceived);
+ std::swap(_timeRemaining, msg._timeRemaining);
+ }
+}
+
+Message &
+Message::setTimeReceived(uint64_t timeReceived)
+{
+ _timeReceived.SetMilliSecs(timeReceived);
+ return *this;
+}
+
+Message &
+Message::setTimeReceivedNow()
+{
+ _timeReceived.SetNow();
+ return *this;
+}
+
+uint64_t
+Message::getTimeRemainingNow() const
+{
+ return (uint64_t)std::max(0.0, _timeRemaining - _timeReceived.MilliSecsToNow());
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/message.h b/messagebus/src/vespa/messagebus/message.h
new file mode 100644
index 00000000000..6afe305389a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/message.h
@@ -0,0 +1,229 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/fastos/time.h>
+#include <memory>
+#include <vespa/messagebus/routing/route.h>
+#include "routable.h"
+
+namespace mbus {
+
+/**
+ * A Message is a question, a Reply is the answer.
+ */
+class Message : public Routable {
+private:
+ Route _route;
+ FastOS_Time _timeReceived;
+ uint64_t _timeRemaining;
+ bool _retryEnabled;
+ uint32_t _retry;
+
+public:
+ /**
+ * Convenience typedef for an auto pointer to a Message object.
+ */
+ typedef std::unique_ptr<Message> UP;
+
+ /**
+ * Constructs a new instance of this class.
+ */
+ Message();
+
+ /**
+ * If a message is deleted with elements on the callstack, this destructor
+ * will log an error and generate an auto-reply to avoid having the sender
+ * wait indefinetly for a reply.
+ */
+ virtual ~Message();
+
+ // Overrides Routable.
+ virtual void swapState(Routable &rhs);
+
+ /**
+ * Returns the timestamp for when this message was last seen by message
+ * bus. If you are using this to determine message expiration, you should
+ * use {@link #isExpired()} instead.
+ *
+ * @return The timestamp this was last seen.
+ */
+ uint64_t getTimeReceived() const { return (uint64_t)_timeReceived.MilliSecs(); }
+
+ /**
+ * Sets the timestamp for when this message was last seen by message bus to
+ * the given time in milliseconds since epoch. Please see comment on {@link
+ * #isExpired()} for more information on how to determine whether or not a
+ * message has expired. You should never need to call this method yourself,
+ * as it is touched automatically whenever message bus encounters a new
+ * message.
+ *
+ * @param timeReceived The time received in milliseconds.
+ * @return This, to allow chaining.
+ */
+ Message &setTimeReceived(uint64_t timeReceived);
+
+ /**
+ * This is a convenience method to call {@link #setTimeReceived(uint64_t)}
+ * passing the current time as argument.
+ *
+ * @return This, to allow chaining.
+ */
+ Message &setTimeReceivedNow();
+
+ /**
+ * Returns the number of milliseconds that remain before this message times
+ * out. This value is only updated by the network layer, and is therefore
+ * not current. If you are trying to determine message expiration, use
+ * {@link this#isExpired()} instead.
+ *
+ * @return The remaining time in milliseconds.
+ */
+ uint64_t getTimeRemaining() const { return _timeRemaining; }
+
+ /**
+ * Sets the numer of milliseconds that remain before this message times
+ * out. Please see comment on {@link this#isExpired()} for more information
+ * on how to determine whether or not a message has expired.
+ *
+ * @param timeRemaining The number of milliseconds until expiration.
+ * @return This, to allow chaining.
+ */
+ Message &setTimeRemaining(uint64_t timeRemaining) { _timeRemaining = timeRemaining; return *this; }
+
+ /**
+ * Returns the number of milliseconds that remain right now before this
+ * message times out. This is a function of {@link this#getTimeReceived()},
+ * {@link this#getTimeRemaining()} and current time. Whenever a message is
+ * transmitted by message bus, a new remaining time is calculated and
+ * serialized as <code>timeRemaining = timeRemaining - (currentTime -
+ * timeReceived)</code>. This means that we are doing an over-estimate of
+ * remaining time, as we are only factoring in the time used by the
+ * application above message bus.
+ *
+ * @return The remaining time in milliseconds.
+ */
+ uint64_t getTimeRemainingNow() const;
+
+ /**
+ * Returns whether or not this message has expired.
+ *
+ * @return True if {@link this#getTimeRemainingNow()} is less than or equal
+ * to zero.
+ */
+ bool isExpired() { return getTimeRemainingNow() == 0; }
+
+ /**
+ * Access the route associated with this message.
+ *
+ * @return reference to internal route object
+ */
+ Route &getRoute() { return _route; }
+
+ /**
+ * Access the route associated with this message.
+ *
+ * @return reference to internal route object
+ */
+ const Route &getRoute() const { return _route; }
+
+ /**
+ * Set a new route for this routable.
+ *
+ * @param route The new route.
+ * @return This, to allow chaining.
+ */
+ Message &setRoute(const Route &route) { _route = route; return *this; }
+
+ /**
+ * Inherited from Routable. Classifies this object as 'not a reply'.
+ *
+ * @return false
+ */
+ virtual bool isReply() const { return false; }
+
+ /**
+ * Returns whether or not this message contains a sequence identifier that
+ * should be respected, i.e. whether or not this message requires
+ * sequencing.
+ *
+ * @return True to enable sequencing.
+ */
+ virtual bool hasSequenceId() const { return false; }
+
+ /**
+ * Returns the identifier used to order messages. Any two messages that have
+ * the same sequence id are ensured to arrive at the recipient in the order
+ * they were sent by the client. This value is only respected if the {@link
+ * #hasSequenceId()} method returns true.
+ *
+ * @return The sequence identifier.
+ */
+ virtual uint64_t getSequenceId() const { return 0; }
+
+ /**
+ * Returns whether or not this message contains a sequence bucket that
+ * should be respected, i.e. whether or not this message requires
+ * bucket-level sequencing.
+ *
+ * @return True to enable bucket sequencing.
+ */
+ virtual bool hasBucketSequence() { return false; }
+
+ /**
+ * Returns the identifier used to order message buckets. Any two messages
+ * that have the same bucket sequence are ensured to arrive at the NEXT peer
+ * in the order they were sent by THIS peer. This value is only respected if
+ * the {@link #hasBucketSequence()} method returns true.
+ *
+ * @return The bucket sequence.
+ */
+ virtual uint64_t getBucketSequence() { return 0; }
+
+ /**
+ * Obtain the approximate size of this message object in bytes. This enables
+ * messagebus to track the size of the send queue in both memory usage and
+ * item count. This method returns 1 by default, and must be overridden to
+ * enable message size tracking.
+ *
+ * @return 1
+ */
+ virtual uint32_t getApproxSize() const { return 1; }
+
+ /**
+ * Sets whether or not this message can be resent.
+ *
+ * @param enabled Resendable flag.
+ */
+ void setRetryEnabled(bool enabled) { _retryEnabled = enabled; }
+
+ /**
+ * Returns whether or not this message can be resent.
+ *
+ * @return True if this can be resent.
+ */
+ bool getRetryEnabled() const { return _retryEnabled; }
+
+ /**
+ * Returns the number of times the sending of this message has been
+ * retried. This is available for inspection so that clients may implement
+ * logic to control resending.
+ *
+ * @see Reply#setRetry This method can be used to request resending that
+ * differs from the default.
+ * @return The retry count.
+ */
+ uint32_t getRetry() const { return _retry; }
+
+ /**
+ * Sets the number of times the sending of this message has been
+ * retried. This method only makes sense to modify BEFORE sending it, since
+ * its value is not serialized back into any reply that it may create.
+ *
+ * @param retry The retry count.
+ * @return This, to allow chaining.
+ */
+ Message &setRetry(uint32_t retry) { _retry = retry; return *this; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/messagebus.cpp b/messagebus/src/vespa/messagebus/messagebus.cpp
new file mode 100644
index 00000000000..87247a90102
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/messagebus.cpp
@@ -0,0 +1,424 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".messagebus");
+
+#include <vespa/messagebus/routing/routingnode.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "messagebus.h"
+#include "imessagehandler.h"
+#include "emptyreply.h"
+#include "errorcode.h"
+#include "sendproxy.h"
+
+using vespalib::LockGuard;
+
+namespace {
+
+/**
+ * Implements a task for running the resender in the messenger thread. This task
+ * acts as a proxy for the resender, allowing the task to be deleted without
+ * affecting the resender itself.
+ */
+class ResenderTask : public mbus::Messenger::ITask {
+private:
+ mbus::Resender *_resender;
+
+public:
+ ResenderTask(mbus::Resender &resender)
+ : _resender(&resender)
+ {
+ // empty
+ }
+
+ void run() {
+ _resender->resendScheduled();
+ }
+
+ uint8_t priority() const {
+ return 255;
+ }
+};
+
+/**
+ * Implements a task for monitoring shutdown of the messenger thread. This task
+ * helps to determine whether or not there is any work left in either the
+ * messenger or network thread.
+ */
+class ShutdownTask : public mbus::Messenger::ITask {
+private:
+ mbus::INetwork &_net;
+ mbus::Messenger &_msn;
+ bool &_done;
+ vespalib::Gate &_gate;
+
+public:
+ ShutdownTask(mbus::INetwork &net, mbus::Messenger &msn,
+ bool &done, vespalib::Gate &gate)
+ : _net(net),
+ _msn(msn),
+ _done(done),
+ _gate(gate)
+ {
+ // empty
+ }
+
+ ~ShutdownTask() {
+ _gate.countDown();
+ }
+
+ void run() {
+ _net.postShutdownHook();
+ _done = _msn.isEmpty();
+ }
+
+ uint8_t priority() const {
+ return 255;
+ }
+};
+
+} // anonymous
+
+namespace mbus {
+
+MessageBus::MessageBus(INetwork &net, ProtocolSet protocols) :
+ _network(net),
+ _lock("mbus::MessageBus::_lock", false),
+ _routingTables(),
+ _sessions(),
+ _protocolRepository(),
+ _msn(),
+ _resender(),
+ _maxPendingCount(0),
+ _maxPendingSize(0),
+ _pendingCount(0),
+ _pendingSize(0)
+{
+ MessageBusParams params;
+ while (!protocols.empty()) {
+ IProtocol::SP protocol = protocols.extract();
+ if (protocol.get() != NULL) {
+ params.addProtocol(protocol);
+ }
+ }
+ setup(params);
+}
+
+MessageBus::MessageBus(INetwork &net, const MessageBusParams &params) :
+ _network(net),
+ _lock("mbus::MessageBus::_lock", false),
+ _routingTables(),
+ _sessions(),
+ _protocolRepository(),
+ _msn(),
+ _resender(),
+ _maxPendingCount(params.getMaxPendingCount()),
+ _maxPendingSize(params.getMaxPendingSize()),
+ _pendingCount(0),
+ _pendingSize(0)
+{
+ setup(params);
+}
+
+MessageBus::~MessageBus()
+{
+ // all sessions must have been destroyed prior to this,
+ // so no more traffic from clients
+ _msn.discardRecurrentTasks(); // no more traffic from recurrent tasks
+ _network.shutdown(); // no more traffic from network
+
+ bool done = false;
+ while (!done) {
+ vespalib::Gate gate;
+ Messenger::ITask::UP task(new ShutdownTask(_network, _msn, done, gate));
+ _msn.enqueue(std::move(task));
+ gate.await();
+ }
+}
+
+void
+MessageBus::setup(const MessageBusParams &params)
+{
+ // Add all known protocols to the repository.
+ for (uint32_t i = 0, len = params.getNumProtocols(); i < len; ++i) {
+ _protocolRepository.putProtocol(params.getProtocol(i));
+ }
+
+ // Attach and start network.
+ _network.attach(*this);
+ if (!_network.start()) {
+ throw vespalib::NetworkSetupFailureException("Failed to start network.");
+ }
+ if (!_network.waitUntilReady(120)) {
+ throw vespalib::NetworkSetupFailureException("Network failed to become ready in time.");
+ }
+
+ // Start messenger.
+ IRetryPolicy::SP retryPolicy = params.getRetryPolicy();
+ if (retryPolicy.get() != NULL) {
+ _resender.reset(new Resender(retryPolicy));
+
+ Messenger::ITask::UP task(new ResenderTask(*_resender));
+ _msn.addRecurrentTask(std::move(task));
+ }
+ if (!_msn.start()) {
+ throw vespalib::NetworkSetupFailureException("Failed to start messenger.");
+ }
+}
+
+SourceSession::UP
+MessageBus::createSourceSession(IReplyHandler &handler)
+{
+ return createSourceSession(SourceSessionParams().setReplyHandler(handler));
+}
+
+SourceSession::UP
+MessageBus::createSourceSession(IReplyHandler &handler,
+ const SourceSessionParams &params)
+{
+ return createSourceSession(SourceSessionParams(params).setReplyHandler(handler));
+}
+
+SourceSession::UP
+MessageBus::createSourceSession(const SourceSessionParams &params)
+{
+ return SourceSession::UP(new SourceSession(*this, params));
+}
+
+IntermediateSession::UP
+MessageBus::createIntermediateSession(const string &name,
+ bool broadcastName,
+ IMessageHandler &msgHandler,
+ IReplyHandler &replyHandler)
+{
+ return createIntermediateSession(IntermediateSessionParams()
+ .setName(name)
+ .setBroadcastName(broadcastName)
+ .setMessageHandler(msgHandler)
+ .setReplyHandler(replyHandler));
+}
+
+IntermediateSession::UP
+MessageBus::createIntermediateSession(const IntermediateSessionParams &params)
+{
+ LockGuard guard(_lock);
+ IntermediateSession::UP ret(new IntermediateSession(*this, params));
+ _sessions[params.getName()] = ret.get();
+ if (params.getBroadcastName()) {
+ _network.registerSession(params.getName());
+ }
+ return ret;
+}
+
+DestinationSession::UP
+MessageBus::createDestinationSession(const string &name,
+ bool broadcastName,
+ IMessageHandler &handler)
+{
+ return createDestinationSession(DestinationSessionParams()
+ .setName(name)
+ .setBroadcastName(broadcastName)
+ .setMessageHandler(handler));
+}
+
+DestinationSession::UP
+MessageBus::createDestinationSession(const DestinationSessionParams &params)
+{
+ LockGuard guard(_lock);
+ DestinationSession::UP ret(new DestinationSession(*this, params));
+ _sessions[params.getName()] = ret.get();
+ if (params.getBroadcastName()) {
+ _network.registerSession(params.getName());
+ }
+ return ret;
+}
+
+void
+MessageBus::unregisterSession(const string &sessionName)
+{
+ LockGuard guard(_lock);
+ _network.unregisterSession(sessionName);
+ _sessions.erase(sessionName);
+}
+
+RoutingTable::SP
+MessageBus::getRoutingTable(const string &protocol)
+{
+ typedef std::map<string, RoutingTable::SP>::iterator ITR;
+ LockGuard guard(_lock);
+ ITR itr = _routingTables.find(protocol);
+ if (itr == _routingTables.end()) {
+ return RoutingTable::SP(); // not found
+ }
+ return itr->second;
+}
+
+IRoutingPolicy::SP
+MessageBus::getRoutingPolicy(const string &protocolName,
+ const string &policyName,
+ const string &policyParam)
+{
+ return _protocolRepository.getRoutingPolicy(protocolName, policyName, policyParam);
+}
+
+void
+MessageBus::sync()
+{
+ _msn.sync();
+ _network.sync(); // should not be necessary, as msn is intermediate
+}
+
+void
+MessageBus::handleMessage(Message::UP msg)
+{
+ if (_resender.get() != NULL && msg->hasBucketSequence()) {
+ deliverError(std::move(msg), ErrorCode::SEQUENCE_ERROR,
+ "Bucket sequences not supported when resender is enabled.");
+ return;
+ }
+ SendProxy &proxy = *(new SendProxy(*this, _network, _resender.get())); // deletes self
+ _msn.deliverMessage(std::move(msg), proxy);
+}
+
+bool
+MessageBus::setupRouting(const RoutingSpec &spec)
+{
+ std::map<string, RoutingTable::SP> rtm;
+ for (uint32_t i = 0; i < spec.getNumTables(); ++i) {
+ const RoutingTableSpec &cfg = spec.getTable(i);
+ IProtocol::SP protocol = getProtocol(cfg.getProtocol());
+ if (protocol.get() == 0) { // protocol not found
+ LOG(info, "Protocol '%s' is not supported, ignoring routing table.",
+ cfg.getProtocol().c_str());
+ continue;
+ }
+ RoutingTable::SP rt(new RoutingTable(cfg));
+ rtm[cfg.getProtocol()] = rt;
+ }
+ {
+ LockGuard guard(_lock);
+ std::swap(_routingTables, rtm);
+ }
+ _protocolRepository.clearPolicyCache();
+ return true;
+}
+
+IProtocol::SP
+MessageBus::getProtocol(const string &name)
+{
+ return _protocolRepository.getProtocol(name);
+}
+
+IProtocol::SP
+MessageBus::putProtocol(const IProtocol::SP & protocol)
+{
+ return _protocolRepository.putProtocol(protocol);
+}
+
+bool
+MessageBus::checkPending(Message &msg)
+{
+ bool busy = false;
+ const uint32_t size = msg.getApproxSize();
+ {
+ constexpr auto relaxed = std::memory_order_relaxed;
+ const uint32_t maxCount = _maxPendingCount.load(relaxed);
+ const uint32_t maxSize = _maxPendingSize.load(relaxed);
+ if (maxCount > 0 || maxSize > 0) {
+ busy = ((maxCount > 0 && _pendingCount.load(relaxed) >= maxCount) ||
+ (maxSize > 0 && _pendingSize.load(relaxed) >= maxSize));
+ if (!busy) {
+ _pendingCount.fetch_add(1, relaxed);
+ _pendingSize.fetch_add(size, relaxed);
+ }
+ }
+ }
+ if (busy) {
+ return false;
+ }
+ msg.setContext(Context(static_cast<uint64_t>(size)));
+ msg.pushHandler(*this, *this);
+ return true;
+}
+
+void
+MessageBus::handleReply(Reply::UP reply)
+{
+ _pendingCount.fetch_sub(1, std::memory_order_relaxed);
+ _pendingSize.fetch_sub(reply->getContext().value.UINT64,
+ std::memory_order_relaxed);
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ deliverReply(std::move(reply), handler);
+}
+
+void
+MessageBus::handleDiscard(Context ctx)
+{
+ _pendingCount.fetch_sub(1, std::memory_order_relaxed);
+ _pendingSize.fetch_sub(ctx.value.UINT64, std::memory_order_relaxed);
+}
+
+void
+MessageBus::deliverMessage(Message::UP msg, const string &session)
+{
+ IMessageHandler *msgHandler = NULL;
+ {
+ LockGuard guard(_lock);
+ std::map<string, IMessageHandler*>::iterator it = _sessions.find(session);
+ if (it != _sessions.end()) {
+ msgHandler = it->second;
+ }
+ }
+ if (msgHandler == NULL) {
+ deliverError(std::move(msg), ErrorCode::UNKNOWN_SESSION,
+ vespalib::make_vespa_string(
+ "Session '%s' does not exist.",
+ session.c_str()));
+ } else if (!checkPending(*msg)) {
+ deliverError(std::move(msg), ErrorCode::SESSION_BUSY,
+ vespalib::make_vespa_string(
+ "Session '%s' is busy, try again later.",
+ session.c_str()));
+ } else {
+ _msn.deliverMessage(std::move(msg), *msgHandler);
+ }
+}
+
+void
+MessageBus::deliverError(Message::UP msg, uint32_t errCode, const string &errMsg)
+{
+ Reply::UP reply(new EmptyReply());
+ reply->swapState(*msg);
+ reply->addError(Error(errCode, errMsg));
+
+ IReplyHandler &replyHandler = reply->getCallStack().pop(*reply);
+ deliverReply(std::move(reply), replyHandler);
+}
+
+void
+MessageBus::deliverReply(Reply::UP reply, IReplyHandler &handler)
+{
+ _msn.deliverReply(std::move(reply), handler);
+}
+
+const string
+MessageBus::getConnectionSpec() const
+{
+ return _network.getConnectionSpec();
+}
+
+void
+MessageBus::setMaxPendingCount(uint32_t maxCount)
+{
+ _maxPendingCount.store(maxCount, std::memory_order_relaxed);
+}
+
+void
+MessageBus::setMaxPendingSize(uint32_t maxSize)
+{
+ _maxPendingSize.store(maxSize, std::memory_order_relaxed);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/messagebus.h b/messagebus/src/vespa/messagebus/messagebus.h
new file mode 100644
index 00000000000..5bcfdef20a1
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/messagebus.h
@@ -0,0 +1,308 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <map>
+#include <vespa/messagebus/network/inetworkowner.h>
+#include <vespa/messagebus/routing/resender.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/routing/routingtable.h>
+#include <vespa/vespalib/util/sync.h>
+#include "destinationsession.h"
+#include "iconfighandler.h"
+#include "idiscardhandler.h"
+#include "intermediatesession.h"
+#include "messagebusparams.h"
+#include "messenger.h"
+#include "protocolset.h"
+#include "protocolrepository.h"
+#include "sourcesession.h"
+#include <string>
+#include <atomic>
+
+namespace mbus {
+
+class SendProxy;
+
+/**
+ * A MessageBus object combined with an INetwork implementation makes up the central part of a messagebus setup. It is
+ * important that the application destructs all sessions before destructing the MessageBus object. Also, the INetwork
+ * object should be destructed after the MessageBus object.
+ */
+class MessageBus : public IMessageHandler,
+ public IConfigHandler,
+ public IDiscardHandler,
+ public INetworkOwner,
+ public IReplyHandler
+{
+private:
+ INetwork &_network;
+ vespalib::Lock _lock;
+ std::map<string, RoutingTable::SP> _routingTables;
+ std::map<string, IMessageHandler*> _sessions;
+ ProtocolRepository _protocolRepository;
+ Messenger _msn;
+ Resender::UP _resender;
+ std::atomic<uint32_t> _maxPendingCount;
+ std::atomic<uint32_t> _maxPendingSize;
+ std::atomic<uint32_t> _pendingCount;
+ std::atomic<uint32_t> _pendingSize;
+
+ /**
+ * This method performs the common constructor tasks.
+ *
+ * @param params The parameters to base setup on.
+ */
+ void setup(const MessageBusParams &params);
+
+ /**
+ * This method handles choking input data so that message bus does not blindly accept everything. This prevents an
+ * application running out-of-memory in case it fail to choke input data itself. If this method returns false, it
+ * means that it should be rejected.
+ *
+ * @param msg The message to count.
+ * @return True if the message was accepted.
+ */
+ bool checkPending(Message &msg);
+
+ /**
+ * Constructs and schedules a Reply containing an error to the handler of the given Message.
+ *
+ * @param msg The message to reply to.
+ * @param errCode The code of the error to set.
+ * @param errMsg The message of the error to set.
+ */
+ void deliverError(Message::UP msg, uint32_t errCode, const string &errMsg);
+
+public:
+ /**
+ * Convenience constructor that proxies {@link this#MessageBus(Network, MessageBusParams)} by adding the given
+ * protocols to a default {@link MessageBusParams} object.
+ *
+ * @param network The network to associate with.
+ * @param protocols An array of protocols to register.
+ */
+ MessageBus(INetwork &net, ProtocolSet protocols);
+
+ /**
+ * Constructs an instance of message bus. This requires a network object that it will associate with. This
+ * assignment may not change during the lifetime of this message bus.
+ *
+ * @param network The network to associate with.
+ * @param params The parameters that controls this bus.
+ */
+ MessageBus(INetwork &net, const MessageBusParams &params);
+
+ /**
+ * Destruct. The destructor will shut down the underlying INetwork object.
+ **/
+ virtual ~MessageBus();
+
+ /**
+ * This is a convenience method to call {@link this#createSourceSession(SourceSessionParams)} with default
+ * values for the {@link SourceSessionParams} object.
+ *
+ * @param handler The reply handler to receive the replies for the session.
+ * @return The created session.
+ */
+ SourceSession::UP createSourceSession(IReplyHandler &handler);
+
+ /**
+ * This is a convenience method to call {@link this#createSourceSession(SourceSessionParams)} by first
+ * assigning the reply handler to the parameter object.
+ *
+ * @param handler The reply handler to receive the replies for the session.
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ SourceSession::UP createSourceSession(IReplyHandler &handler,
+ const SourceSessionParams &params);
+
+ /**
+ * Creates a source session on top of this message bus.
+ *
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ SourceSession::UP createSourceSession(const SourceSessionParams &params);
+
+ /**
+ * This is a convenience method to call {@link this#createIntermediateSession(IntermediateSessionParams)} with
+ * default values for the {@link IntermediateSessionParams} object.
+ *
+ * @param name The local unique name for the created session.
+ * @param broadcastName Whether or not to broadcast this session's name on the network.
+ * @param msgHandler The handler to receive the messages for the session.
+ * @param replyHandler The handler to received the replies for the session.
+ * @return The created session.
+ */
+ IntermediateSession::UP createIntermediateSession(const string &name,
+ bool broadcastName,
+ IMessageHandler &msgHandler,
+ IReplyHandler &replyHandler);
+
+ /**
+ * Creates an intermediate session on top of this message bus using the given handlers and parameter object.
+ *
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ IntermediateSession::UP createIntermediateSession(const IntermediateSessionParams &params);
+
+ /**
+ * This is a convenience method to call {@link this#createDestinationSession(DestinationSessionParams)} with default
+ * values for the {@link DestinationSessionParams} object.
+ *
+ * @param name The local unique name for the created session.
+ * @param broadcastName Whether or not to broadcast this session's name on the network.
+ * @param handler The handler to receive the messages for the session.
+ * @return The created session.
+ */
+ DestinationSession::UP createDestinationSession(const string &name,
+ bool broadcastName,
+ IMessageHandler &handler);
+
+ /**
+ * Creates a destination session on top of this message bus using the given handlers and parameter object.
+ *
+ * @param params The parameters to control the session.
+ * @return The created session.
+ */
+ DestinationSession::UP createDestinationSession(const DestinationSessionParams &params);
+
+ /**
+ * Unregister a session. This method is invoked by session destructors to ensure that no more Message objects are
+ * delivered and that the session name is removed from the network naming service. The sync method can be invoked
+ * after invoking this one to ensure that no callbacks are active.
+ *
+ * @param sessionName name of the session to unregister
+ **/
+ void unregisterSession(const string &sessionName);
+
+ /**
+ * Obtain the routing table for the given protocol. If the appropriate routing table could not be found, a shared
+ * pointer to 0 is returned.
+ *
+ * @return shared pointer to routing table
+ * @param protocol the protocol name
+ **/
+ RoutingTable::SP getRoutingTable(const string &protocol);
+
+ /**
+ * Returns a routing policy that corresponds to the argument protocol name, policy name and policy parameter. This
+ * will cache reuse all policies as soon as they are first requested.
+ *
+ * @param protocol The name of the protocol to invoke {@link Protocol#createPolicy(String,String)} on.
+ * @param policyName The name of the routing policy to retrieve.
+ * @param policyParam The parameter for the routing policy to retrieve.
+ * @return A corresponding routing policy, or null.
+ */
+ IRoutingPolicy::SP getRoutingPolicy(const string &protocol, const string &policyName,
+ const string &policyParam);
+
+ /**
+ * Synchronize with internal threads. This method will handshake with all internal threads. This has the implicit
+ * effect of waiting for all active callbacks. Note that this method should never be invoked from a callback since
+ * that would make the thread wait for itself... forever. This method is typically used to untangle during session
+ * destruction.
+ **/
+ void sync();
+
+ /**
+ * Returns the resender that is running within this message bus.
+ *
+ * @return The resender.
+ */
+ Resender *getResender() { return _resender.get(); }
+
+ /**
+ * Returns the number of messages received that have not been replied to yet.
+ *
+ * @return The pending count.
+ */
+ uint32_t getPendingCount() const { return _pendingCount; }
+
+ /**
+ * Returns the size of messages received that have not been replied to yet.
+ *
+ * @return The pending size.
+ */
+ uint32_t getPendingSize() const { return _pendingSize; }
+
+ /**
+ * Sets the maximum number of messages that can be received without being replied to yet.
+ *
+ * @param maxCount The max count.
+ */
+ void setMaxPendingCount(uint32_t maxCount);
+
+ /**
+ * Gets maximum number of messages that can be received without being
+ * replied to yet.
+ */
+ uint32_t getMaxPendingCount() const noexcept {
+ return _maxPendingCount.load(std::memory_order_relaxed);
+ }
+
+ /**
+ * Sets the maximum size of messages that can be received without being replied to yet.
+ *
+ * @param maxSize The max size.
+ */
+ void setMaxPendingSize(uint32_t maxSize);
+
+ /**
+ * Gets maximum combined size of messages that can be received without
+ * being replied to yet.
+ */
+ uint32_t getMaxPendingSize() const noexcept {
+ return _maxPendingSize.load(std::memory_order_relaxed);
+ }
+
+ /**
+ * Adds a protocol to the internal repository of protocols, replacing any previous instance of the
+ * protocol and clearing the associated routing policy cache.
+ *
+ * @param protocol The protocol to add.
+ */
+ IProtocol::SP putProtocol(const IProtocol::SP & protocol);
+
+ /**
+ * Returns the connection spec string for the network layer of this message bus. This is merely a proxy of
+ * the same function in the network layer.
+ *
+ * @return The connection string.
+ */
+ const string getConnectionSpec() const;
+
+ /**
+ * Provide access to the underlying {@link Messenger} object.
+ *
+ * @return The underlying {@link Messenger} object.
+ */
+ Messenger & getMessenger() { return _msn; }
+
+ // Implements IReplyHandler.
+ void handleReply(Reply::UP reply);
+
+ // Implements IDiscardHandler.
+ void handleDiscard(Context ctx);
+
+ // Implements IMessageHandler.
+ void handleMessage(Message::UP msg);
+
+ // Implements IConfigHandler.
+ bool setupRouting(const RoutingSpec &spec);
+
+ // Implements INetworkOwner.
+ IProtocol::SP getProtocol(const string &name);
+
+ // Implements INetworkOwner.
+ void deliverMessage(Message::UP msg, const string &session);
+
+ // Implements INetworkOwner.
+ void deliverReply(Reply::UP reply, IReplyHandler &handler);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/messagebusparams.cpp b/messagebus/src/vespa/messagebus/messagebusparams.cpp
new file mode 100644
index 00000000000..f1a609086d6
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/messagebusparams.cpp
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include "messagebus.h"
+
+namespace mbus {
+
+MessageBusParams::MessageBusParams() :
+ _protocols(),
+ _retryPolicy(new RetryTransientErrorsPolicy()),
+ _maxPendingCount(1024),
+ _maxPendingSize(128 * 1024 * 1024)
+{
+ // empty
+}
+
+uint32_t
+MessageBusParams::getNumProtocols() const
+{
+ return _protocols.size();
+}
+
+IProtocol::SP
+MessageBusParams::getProtocol(uint32_t i) const
+{
+ return _protocols[i];
+}
+
+MessageBusParams &
+MessageBusParams::addProtocol(IProtocol::SP protocol)
+{
+ _protocols.push_back(protocol);
+ return *this;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/messagebusparams.h b/messagebus/src/vespa/messagebus/messagebusparams.h
new file mode 100644
index 00000000000..6dbcfb8781f
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/messagebusparams.h
@@ -0,0 +1,103 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/messagebus/routing/iretrypolicy.h>
+#include <string>
+#include <vector>
+#include "iprotocol.h"
+
+namespace mbus {
+
+class MessageBus;
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus} constructor, all parameters are held by this
+ * class. This class has reasonable default values for each parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class MessageBusParams {
+private:
+ std::vector<IProtocol::SP> _protocols;
+ IRetryPolicy::SP _retryPolicy;
+ uint32_t _maxPendingCount;
+ uint32_t _maxPendingSize;
+
+public:
+ /**
+ * Constructs a new instance of this parameter object with default values for all members.
+ */
+ MessageBusParams();
+
+ /**
+ * Returns the retry policy for the resender.
+ *
+ * @return The policy.
+ */
+ IRetryPolicy::SP getRetryPolicy() const { return _retryPolicy; }
+
+ /**
+ * Sets the retry policy for the resender.
+ *
+ * @param retryPolicy The policy to set.
+ * @return This, to allow chaining.
+ */
+ MessageBusParams &setRetryPolicy(IRetryPolicy::SP retryPolicy) { _retryPolicy = retryPolicy; return *this; }
+
+ /**
+ * Registers a protocol under the name given by {@link com.yahoo.messagebus.Protocol#getName()}.
+ *
+ * @param protocol The protocol to register.
+ * @return This, to allow chaining.
+ */
+ MessageBusParams &addProtocol(IProtocol::SP protocol);
+
+ /**
+ * Returns the number of protocols that are contained in this.
+ *
+ * @return The number of protocols.
+ */
+ uint32_t getNumProtocols() const;
+
+ /**
+ * Returns the protocol at the given index.
+ *
+ * @param i The index of the protocol to return.
+ * @return The protocol object.
+ */
+ IProtocol::SP getProtocol(uint32_t i) const;
+
+ /**
+ * Returns the maximum number of pending messages.
+ *
+ * @return The count limit.
+ */
+ uint32_t getMaxPendingCount() const { return _maxPendingCount; }
+
+ /**
+ * Sets the maximum number of allowed pending messages.
+ *
+ * @param maxCount The count limit to set.
+ * @return This, to allow chaining.
+ */
+ MessageBusParams &setMaxPendingCount(uint32_t maxCount) { _maxPendingCount = maxCount; return *this; }
+
+ /**
+ * Returns the maximum number of bytes allowed for pending messages.
+ *
+ * @return The size limit.
+ */
+ uint32_t getMaxPendingSize() const { return _maxPendingSize; }
+
+ /**
+ * Sets the maximum number of bytes allowed for pending messages.
+ *
+ * @param maxSize The size limit to set.
+ * @return This, to allow chaining.
+ */
+ MessageBusParams &setMaxPendingSize(int maxSize) { _maxPendingSize = maxSize; return *this; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/messenger.cpp b/messagebus/src/vespa/messagebus/messenger.cpp
new file mode 100644
index 00000000000..3727b258a89
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/messenger.cpp
@@ -0,0 +1,286 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".messenger");
+
+#include <vespa/vespalib/util/sync.h>
+#include "messenger.h"
+
+namespace {
+
+template<class T>
+struct DeleteFunctor
+{
+ bool operator()(T *ptr) const
+ {
+ delete ptr;
+ return true;
+ }
+};
+
+class MessageTask : public mbus::Messenger::ITask {
+private:
+ mbus::Message::UP _msg;
+ mbus::IMessageHandler &_handler;
+
+public:
+ MessageTask(mbus::Message::UP msg, mbus::IMessageHandler &handler)
+ : _msg(std::move(msg)),
+ _handler(handler)
+ {
+ // empty
+ }
+
+ ~MessageTask() {
+ if (_msg.get() != NULL) {
+ _msg->discard();
+ }
+ }
+
+ void run() {
+ _handler.handleMessage(std::move(_msg));
+ }
+
+ uint8_t priority() const {
+ if (_msg.get() != NULL) {
+ return _msg->priority();
+ }
+
+ return 255;
+ }
+};
+
+class ReplyTask : public mbus::Messenger::ITask {
+private:
+ mbus::Reply::UP _reply;
+ mbus::IReplyHandler &_handler;
+
+public:
+ ReplyTask(mbus::Reply::UP reply, mbus::IReplyHandler &handler)
+ : _reply(std::move(reply)),
+ _handler(handler)
+ {
+ // empty
+ }
+
+ ~ReplyTask() {
+ if (_reply.get() != NULL) {
+ _reply->discard();
+ }
+ }
+
+ void run() {
+ _handler.handleReply(std::move(_reply));
+ }
+
+ uint8_t priority() const {
+ if (_reply.get() != NULL) {
+ return _reply->priority();
+ }
+
+ return 255;
+ }
+};
+
+class SyncTask : public mbus::Messenger::ITask {
+private:
+ vespalib::Gate &_gate;
+
+public:
+ SyncTask(vespalib::Gate &gate)
+ : _gate(gate)
+ {
+ // empty
+ }
+
+ ~SyncTask() {
+ _gate.countDown();
+ }
+
+ void run() {
+ // empty
+ }
+
+ uint8_t priority() const {
+ return 255;
+ }
+};
+
+class AddRecurrentTask : public mbus::Messenger::ITask {
+private:
+ std::vector<ITask*> &_tasks;
+ mbus::Messenger::ITask::UP _task;
+
+public:
+ AddRecurrentTask(std::vector<ITask*> &tasks, mbus::Messenger::ITask::UP task)
+ : _tasks(tasks),
+ _task(std::move(task))
+ {
+ // empty
+ }
+
+ void run() {
+ _tasks.push_back(_task.release());
+ }
+
+ uint8_t priority() const {
+ return 255;
+ }
+};
+
+class DiscardRecurrentTasks : public SyncTask {
+private:
+ std::vector<ITask*> &_tasks;
+
+public:
+ DiscardRecurrentTasks(vespalib::Gate &gate, std::vector<ITask*> &tasks)
+ : SyncTask(gate),
+ _tasks(tasks)
+ {
+ // empty
+ }
+
+ void run() {
+ std::for_each(_tasks.begin(), _tasks.end(), DeleteFunctor<ITask>());
+ _tasks.clear();
+ SyncTask::run();
+ }
+
+ uint8_t priority() const {
+ return 255;
+ }
+};
+
+} // anonymous
+
+namespace mbus {
+
+Messenger::Messenger() :
+ _monitor(),
+ _pool(128000),
+ _children(),
+ _queue(),
+ _closed(false)
+{
+ // empty
+}
+
+Messenger::~Messenger()
+{
+ {
+ vespalib::MonitorGuard guard(_monitor);
+ _closed = true;
+ guard.broadcast();
+ }
+ _pool.Close();
+ std::for_each(_children.begin(), _children.end(), DeleteFunctor<ITask>());
+ if ( ! _queue.empty()) {
+ LOG(warning,
+ "Messenger shut down with pending tasks, "
+ "please review shutdown logic.");
+ while (!_queue.empty()) {
+ delete _queue.front();
+ _queue.pop();
+ }
+ }
+}
+
+void
+Messenger::Run(FastOS_ThreadInterface *thread, void *arg)
+{
+ (void)thread;
+ (void)arg;
+ while (true) {
+ ITask::UP task;
+ {
+ vespalib::MonitorGuard guard(_monitor);
+ if (_closed) {
+ break;
+ }
+ if (_queue.empty()) {
+ guard.wait(100);
+ }
+ if (!_queue.empty()) {
+ task.reset(_queue.front());
+ _queue.pop();
+ }
+ }
+ if (task.get() != NULL) {
+ try {
+ task->run();
+ } catch (const std::exception &e) {
+ LOG(warning, "An exception was thrown while running "
+ "a task; %s", e.what());
+ }
+ }
+ for (ITask * itask : _children) {
+ itask->run();
+ }
+ }
+}
+
+void
+Messenger::addRecurrentTask(ITask::UP task)
+{
+ ITask::UP add(new AddRecurrentTask(_children, std::move(task)));
+ enqueue(std::move(add));
+}
+
+void
+Messenger::discardRecurrentTasks()
+{
+ vespalib::Gate gate;
+ ITask::UP task(new DiscardRecurrentTasks(gate, _children));
+ enqueue(std::move(task));
+ gate.await();
+}
+
+bool
+Messenger::start()
+{
+ if (_pool.NewThread(this) == 0) {
+ return false;
+ }
+ return true;
+}
+
+void
+Messenger::deliverMessage(Message::UP msg, IMessageHandler &handler)
+{
+ enqueue(ITask::UP(new MessageTask(std::move(msg), handler)));
+}
+
+void
+Messenger::deliverReply(Reply::UP reply, IReplyHandler &handler)
+{
+ enqueue(ITask::UP(new ReplyTask(std::move(reply), handler)));
+}
+
+void
+Messenger::enqueue(ITask::UP task)
+{
+ vespalib::MonitorGuard guard(_monitor);
+ if (!_closed) {
+ _queue.push(task.release());
+ if (_queue.size() == 1) {
+ guard.signal();
+ }
+ }
+}
+
+void
+Messenger::sync()
+{
+ vespalib::Gate gate;
+ enqueue(ITask::UP(new SyncTask(gate)));
+ gate.await();
+}
+
+bool
+Messenger::isEmpty() const
+{
+ vespalib::MonitorGuard guard(_monitor);
+ return _queue.empty();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/messenger.h b/messagebus/src/vespa/messagebus/messenger.h
new file mode 100644
index 00000000000..34c02ed1cad
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/messenger.h
@@ -0,0 +1,128 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/util/executor.h>
+#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/arrayqueue.hpp>
+#include "imessagehandler.h"
+#include "ireplyhandler.h"
+#include "message.h"
+#include "reply.h"
+
+namespace mbus {
+
+/**
+ * This class implements a single thread that is able to process arbitrary
+ * tasks. Tasks are enqueued using the synchronized {@link #enqueue(Task)}
+ * method, and are run in the order they were enqueued.
+ */
+class Messenger : public boost::noncopyable,
+ public FastOS_Runnable {
+public:
+ /**
+ * Defines the required interface for tasks to be posted to this worker.
+ */
+ class ITask : public boost::noncopyable,
+ public vespalib::Executor::Task {
+ public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<ITask> UP;
+
+ /**
+ * Returns the priority of this task.
+ */
+ virtual uint8_t priority() const = 0;
+ };
+
+private:
+ vespalib::Monitor _monitor;
+ FastOS_ThreadPool _pool;
+ std::vector<ITask*> _children;
+ vespalib::ArrayQueue<ITask*> _queue;
+ bool _closed;
+
+protected:
+ // Implements FastOS_Runnable.
+ void Run(FastOS_ThreadInterface *thread, void *arg);
+
+public:
+ /**
+ * Constructs a new messenger object.
+ */
+ Messenger();
+
+ /**
+ * Frees any allocated resources. Also destroys all queued tasks.
+ */
+ ~Messenger();
+
+ /**
+ * Adds a recurrent task to this that is to be run for every iteration of
+ * the main loop. This task must be very light-weight as to not block the
+ * messenger. This method is thread-safe.
+ *
+ * @param task The task to add.
+ */
+ void addRecurrentTask(ITask::UP task);
+
+ /**
+ * Discards all the recurrent tasks previously added to using the {@link
+ * #addRecurrentTask(ITask)} method. This method is thread-safe.
+ */
+ void discardRecurrentTasks();
+
+ /**
+ * Starts the internal thread. This must be done AFTER all recurrent tasks
+ * have been added.
+ *
+ * @return True if the thread was started.
+ * @see #addRecurrentTask(ITask)
+ */
+ bool start();
+
+ /**
+ * Handshakes with the internal thread. If this method is called using the
+ * messenger thread, this will deadlock.
+ */
+ void sync();
+
+ /**
+ * Convenience method to post a {@link MessageTask} to the queue of tasks to
+ * be executed.
+ *
+ * @param msg The message to send.
+ * @param handler The handler to send to.
+ */
+ void deliverMessage(Message::UP msg, IMessageHandler &handler);
+
+ /**
+ * Convenience method to post a {@link ReplyTask} to the queue of tasks to
+ * be executed.
+ *
+ * @param reply The reply to return.
+ * @param handler The handler to return to.
+ */
+ void deliverReply(Reply::UP reply, IReplyHandler &handler);
+
+ /**
+ * Enqueues the given task in the list of tasks that this worker is to
+ * process. If this thread has been destroyed previously, this method
+ * invokes {@link Messenger.Task#destroy()}.
+ *
+ * @param task The task to enqueue.
+ */
+ void enqueue(ITask::UP task);
+
+ /**
+ * Returns whether or not there are any tasks queued for execution.
+ *
+ * @return True if there are no tasks.
+ */
+ bool isEmpty() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/.gitignore b/messagebus/src/vespa/messagebus/network/.gitignore
new file mode 100644
index 00000000000..5dae353d999
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/.gitignore
@@ -0,0 +1,2 @@
+.depend
+Makefile
diff --git a/messagebus/src/vespa/messagebus/network/CMakeLists.txt b/messagebus/src/vespa/messagebus/network/CMakeLists.txt
new file mode 100644
index 00000000000..33074a33b9d
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/CMakeLists.txt
@@ -0,0 +1,16 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(messagebus_network OBJECT
+ SOURCES
+ identity.cpp
+ oosclient.cpp
+ oosmanager.cpp
+ rpcnetwork.cpp
+ rpcnetworkparams.cpp
+ rpcsendv1.cpp
+ rpcservice.cpp
+ rpcserviceaddress.cpp
+ rpcservicepool.cpp
+ rpctarget.cpp
+ rpctargetpool.cpp
+ DEPENDS
+)
diff --git a/messagebus/src/vespa/messagebus/network/identity.cpp b/messagebus/src/vespa/messagebus/network/identity.cpp
new file mode 100644
index 00000000000..adddbc0c6ec
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/identity.cpp
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".identity");
+#include "identity.h"
+#include <vespa/vespalib/util/host_name.h>
+
+namespace mbus {
+
+Identity::Identity(const string &configId) :
+ _hostname(),
+ _servicePrefix(configId)
+{
+ _hostname = vespalib::HostName::get();
+}
+
+std::vector<string>
+Identity::split(const string &name)
+{
+ std::vector<string> ret;
+ string::size_type pos = 0;
+ string::size_type split = name.find_first_of('/');
+ while (split != string::npos) {
+ ret.push_back(string(name, pos, split - pos));
+ pos = split + 1;
+ split = name.find_first_of('/', pos);
+ }
+ ret.push_back(string(name, pos));
+ return ret;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/identity.h b/messagebus/src/vespa/messagebus/network/identity.h
new file mode 100644
index 00000000000..f93a68bd781
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/identity.h
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/messagebus/common.h>
+#include <vector>
+
+namespace mbus {
+
+/**
+ * An Identity object is a simple value object containing information
+ * about the identity of a Network object within the vespa cluster. It
+ * contains the service name prefix used when registering
+ * sessions. This class also has some static utility methods for
+ * general service name manipulation.
+ **/
+class Identity
+{
+private:
+ string _hostname;
+ string _servicePrefix;
+
+public:
+ /**
+ * Use config to resolve the identity for the given config
+ * id. This is intended to be done once at program
+ * startup. Changing the identity of a service requires
+ * restart. This method will not mask config exceptions.
+ *
+ * @return identity for the given config id
+ * @param configId application config id
+ **/
+ Identity(const string &configId);
+
+ /**
+ * Obtain the hostname held by this object.
+ *
+ * @return hostname
+ **/
+ const string &getHostname() const { return _hostname; }
+
+ /**
+ * Obtain the service prefix held by this object.
+ *
+ * @return service prefix
+ **/
+ const string &getServicePrefix() const { return _servicePrefix; }
+
+ /**
+ * Split a service name into its path elements.
+ *
+ * @return service name path elements
+ * @param name the service name
+ **/
+ static std::vector<string> split(const string &name);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/inetwork.h b/messagebus/src/vespa/messagebus/network/inetwork.h
new file mode 100644
index 00000000000..cf98711bacd
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/inetwork.h
@@ -0,0 +1,145 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <vespa/messagebus/routing/routingnode.h>
+#include <vespa/slobrok/sbmirror.h>
+#include "inetworkowner.h"
+
+namespace mbus {
+
+/**
+ * This interface is used to hide away the implementation details of the network
+ * code from the rest of the messagebus implementation. The methods defined in
+ * this interface is intended to be invoked by MessageBus and not by the
+ * application. The only responsibility of the application is to instantiate an
+ * INetwork implementing object, give it to the MessageBus constructor and make
+ * sure it outlives the MessageBus object.
+ */
+class INetwork {
+public:
+ /**
+ * Destructor. Frees any allocated resources.
+ */
+ virtual ~INetwork() { }
+
+ /**
+ * Attach the network layer to the given owner. This method should be
+ * invoked before starting the network. This method is invoked by the
+ * MessageBus constructor.
+ *
+ * @param owner owner of the network
+ */
+ virtual void attach(INetworkOwner &owner) = 0;
+
+ /**
+ * Returns a string that represents the connection specs of this network. It
+ * is in not a complete address since it know nothing of the sessions that
+ * run on it.
+ *
+ * @return The connection string.
+ */
+ virtual const string getConnectionSpec() const = 0;
+
+ /**
+ * Start this network. This method should be invoked after the attach method
+ * and before starting to use the network. This method is invoked by the
+ * MessageBus constructor.
+ *
+ * @return true if the network could be started
+ */
+ virtual bool start() = 0;
+
+ /**
+ * Waits for at most the given number of seconds for all dependencies to
+ * become ready.
+ *
+ * @param seconds The timeout.
+ * @return True if ready.
+ */
+ virtual bool waitUntilReady(double seconds) const = 0;
+
+ /**
+ * Register a session name with the network layer. This will make the
+ * session visible to other nodes.
+ *
+ * @param session the session name
+ */
+ virtual void registerSession(const string &session) = 0;
+
+ /**
+ * Unregister a session name with the network layer. This will make the
+ * session unavailable for other nodes.
+ *
+ * @param session session name
+ */
+ virtual void unregisterSession(const string &session) = 0;
+
+ /**
+ * Resolves the service address of the recipient referenced by the given
+ * routing node. If a recipient can not be resolved, this method tags the
+ * node with an error. If this method succeeds, you need to invoke {@link
+ * #freeServiceAddress(RoutingNode)} once you are done with the service
+ * address.
+ *
+ * @param recipient The node whose service address to allocate.
+ * @return True if a service address was allocated.
+ */
+ virtual bool allocServiceAddress(RoutingNode &recipient) = 0;
+
+ /**
+ * Frees the service address from the given routing node. This allows the
+ * network layer to track and close connections as required.
+ *
+ * @param recipient The node whose service address to free.
+ */
+ virtual void freeServiceAddress(RoutingNode &recipient) = 0;
+
+ /**
+ * Send a message to the given recipients. A {@link RoutingNode} contains
+ * all the necessary context for sending.
+ *
+ * @param msg The message to send.
+ * @param recipients A list of routing leaf nodes resolved for the message.
+ */
+ virtual void send(const Message &msg, const std::vector<RoutingNode*> &recipients) = 0;
+
+ /**
+ * Synchronize with internal threads. This method will handshake with all
+ * internal threads. This has the implicit effect of waiting for all active
+ * callbacks. Note that this method should never be invoked from a callback
+ * since that would make the thread wait for itself... forever. This method
+ * is typically used to untangle during session destruction. If this method
+ * is invoked after the shutdown method is invoked it will never return.
+ */
+ virtual void sync() = 0;
+
+ /**
+ * Shut down this network. This method will block until the network has been
+ * properly shut down. After the network has been shut down, this method
+ * will also flush out the ghosts in the system. In this case, the ghosts
+ * are the replies waiting to be delivered in a separate thread context, but
+ * having no real end-points since all sessions must be destructed before
+ * destructing the MessageBus object. This method is invoked by the
+ * MessageBus destructor.
+ */
+ virtual void shutdown() = 0;
+
+ /**
+ * If anything is posted to the network after {@link #shutdown()} is called,
+ * there is no thread alive that can generate a reply. By calling this method
+ * you are effectively flushing all ghosts from the network back to their
+ * respective owners.
+ */
+ virtual void postShutdownHook() = 0;
+
+ /**
+ * Returns a reference to a name server mirror.
+ *
+ * @return The mirror object.
+ */
+ virtual const slobrok::api::IMirrorAPI &getMirror() const = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/inetworkowner.h b/messagebus/src/vespa/messagebus/network/inetworkowner.h
new file mode 100644
index 00000000000..493f121783f
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/inetworkowner.h
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <vespa/messagebus/iprotocol.h>
+#include <vespa/messagebus/ireplyhandler.h>
+
+namespace mbus {
+
+/**
+ * A network owner is the object that instantiates and uses a network. The API to send messages
+ * across the network is part of the Network interface, whereas this interface exposes the required
+ * functionality of a network owner to be able to decode and deliver incoming messages.
+ */
+class INetworkOwner {
+public:
+ /**
+ * Required for inheritance.
+ */
+ virtual ~INetworkOwner() { }
+
+ /**
+ * All messages are sent across the network with its accompanying protocol name so that it can be decoded at the
+ * receiving end. The network queries its owner through this function to resolve the protocol from its name.
+ *
+ * @param name The name of the protocol to return.
+ * @return The named protocol.
+ */
+ virtual IProtocol::SP getProtocol(const string &name) = 0;
+
+ /**
+ * All messages that arrive in the network layer is passed to its owner through this function.
+ *
+ * @param message The message that just arrived from the network.
+ * @param session The name of the session that is the recipient of the request.
+ */
+ virtual void deliverMessage(Message::UP message, const string &session) = 0;
+
+ /**
+ * All replies that arrive in the network layer is passed through this to unentangle it from the network thread.
+ *
+ * @param reply The reply that just arrived from the network.
+ * @param handler The handler that is to receive the reply.
+ */
+ virtual void deliverReply(Reply::UP reply, IReplyHandler &handler) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/iserviceaddress.h b/messagebus/src/vespa/messagebus/network/iserviceaddress.h
new file mode 100644
index 00000000000..78f487cc942
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/iserviceaddress.h
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+namespace mbus {
+
+/**
+ * This interface represents an abstract network service; i.e. somewhere to send messages. An instance of this is
+ * retrieved by calling {@link Network#lookup(String)}.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class IServiceAddress {
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<IServiceAddress> UP;
+
+ /**
+ * Virtual destructor required for inheritance.
+ */
+ virtual ~IServiceAddress() { }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/oosclient.cpp b/messagebus/src/vespa/messagebus/network/oosclient.cpp
new file mode 100644
index 00000000000..b25668c1481
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/oosclient.cpp
@@ -0,0 +1,111 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".oosclient");
+#include "oosclient.h"
+
+namespace mbus {
+
+void
+OOSClient::handleReply()
+{
+ if (!_req->CheckReturnTypes("Si")) {
+ _target->SubRef();
+ _target = 0;
+ Schedule(1.0);
+ return;
+ }
+ FRT_Values &ret = *(_req->GetReturn());
+ uint32_t retGen = ret[1]._intval32;
+ if (_reqGen != retGen) {
+ StringList oos;
+ uint32_t numNames = ret[0]._string_array._len;
+ FRT_StringValue *names = ret[0]._string_array._pt;
+ for (uint32_t idx = 0; idx < numNames; ++idx) {
+ oos.push_back(string(names[idx]._str));
+ }
+ _oosList.swap(oos);
+ _reqGen = retGen;
+ _listGen = retGen;
+ }
+ Schedule(0.1);
+}
+
+void
+OOSClient::handleConnect()
+{
+ if (_target == 0) {
+ _target = _orb.GetTarget(_spec.c_str());
+ _reqGen = 0;
+ }
+}
+
+void
+OOSClient::handleInvoke()
+{
+ LOG_ASSERT(_target != 0);
+ _req = _orb.AllocRPCRequest(_req);
+ _req->SetMethodName("fleet.getOOSList");
+ _req->GetParams()->AddInt32(_reqGen); // gencnt
+ _req->GetParams()->AddInt32(60000); // mstimeout
+ _target->InvokeAsync(_req, 70.0, this);
+}
+
+void
+OOSClient::PerformTask()
+{
+ if (_reqDone) {
+ _reqDone = false;
+ handleReply();
+ return;
+ }
+ handleConnect();
+ handleInvoke();
+}
+
+void
+OOSClient::RequestDone(FRT_RPCRequest *req)
+{
+ LOG_ASSERT(req == _req && !_reqDone);
+ (void) req;
+ _reqDone = true;
+ ScheduleNow();
+}
+
+OOSClient::OOSClient(FRT_Supervisor &orb,
+ const string &mySpec)
+ : FNET_Task(orb.GetScheduler()),
+ _orb(orb),
+ _spec(mySpec),
+ _oosList(),
+ _reqGen(0),
+ _listGen(0),
+ _dumpGen(0),
+ _reqDone(false),
+ _target(0),
+ _req(0)
+{
+ ScheduleNow();
+}
+
+OOSClient::~OOSClient()
+{
+ Kill();
+ if (_req != 0) {
+ _req->Abort();
+ _req->SubRef();
+ }
+ if (_target != 0) {
+ _target->SubRef();
+ }
+}
+
+void
+OOSClient::dumpState(StringSet &dst)
+{
+ dst.insert(_oosList.begin(), _oosList.end());
+ _dumpGen = _listGen;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/oosclient.h b/messagebus/src/vespa/messagebus/network/oosclient.h
new file mode 100644
index 00000000000..62f799740ec
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/oosclient.h
@@ -0,0 +1,125 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/fnet/frt/frt.h>
+#include <vespa/messagebus/common.h>
+#include <vector>
+#include <set>
+
+namespace mbus {
+
+/**
+ * This class keeps track of OOS information obtained from a single
+ * server. This class is used by the OOSManager class. Note that since
+ * this class is only used inside the transport thread it has no
+ * synchronization. Using it directly will lead to race conditions and
+ * possible crashes.
+ **/
+class OOSClient : public FNET_Task,
+ public FRT_IRequestWait
+{
+private:
+ typedef std::vector<string> StringList;
+
+ FRT_Supervisor &_orb;
+ string _spec;
+ StringList _oosList;
+ uint32_t _reqGen; // server gen used for request
+ uint32_t _listGen; // server gen of the oosList
+ uint32_t _dumpGen; // server gen used for the last dump
+ bool _reqDone;
+ FRT_Target *_target;
+ FRT_RPCRequest *_req;
+
+ OOSClient(const OOSClient &);
+ OOSClient &operator=(const OOSClient &);
+
+ /**
+ * Handle a server reply.
+ **/
+ void handleReply();
+
+ /**
+ * Handle server (re)connect.
+ **/
+ void handleConnect();
+
+ /**
+ * Handle server invocation.
+ **/
+ void handleInvoke();
+
+ /**
+ * From FNET_Task, performs overall server poll logic.
+ **/
+ void PerformTask();
+
+ /**
+ * From FRT_IRequestWait, picks up server replies.
+ *
+ * @param req the request that has completed
+ **/
+ void RequestDone(FRT_RPCRequest *req);
+
+public:
+ /**
+ * Data structure used to aggregate OOS information
+ **/
+ typedef std::set<string> StringSet;
+
+ /**
+ * Convenience typedef for a shared pointer to a OOSClient object.
+ **/
+ typedef std::shared_ptr<OOSClient> SP;
+
+ /**
+ * Create a new OOSClient polling oos information from the given
+ * server.
+ *
+ * @param orb object used for RPC operations
+ * @param spec fnet connect spec for oos server
+ **/
+ OOSClient(FRT_Supervisor &orb, const string &spec);
+
+ /**
+ * Destructor.
+ **/
+ virtual ~OOSClient();
+
+ /**
+ * Obtain the connect spec of the OOS server this client is
+ * talking to.
+ *
+ * @return OOS server connect spec
+ **/
+ const string &getSpec() const { return _spec; }
+
+ /**
+ * Check if this client has changed. A client has changed if it
+ * has obtain now information after the dumpState method was last
+ * invoked.
+ *
+ * @return true is this client has changed
+ **/
+ bool isChanged() const { return (_listGen != _dumpGen); }
+
+ /**
+ * Returns whether or not this client has receieved any reply
+ * at all from the server it is connected to.
+ *
+ * @return True if initial request has returned.
+ */
+ bool isReady() const { return _listGen != 0; }
+
+ /**
+ * Dump the current oos information known by this client into the
+ * given string set.
+ *
+ * @param dst object used to aggregate oos information
+ **/
+ void dumpState(StringSet &dst);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/oosmanager.cpp b/messagebus/src/vespa/messagebus/network/oosmanager.cpp
new file mode 100644
index 00000000000..15f76c0668f
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/oosmanager.cpp
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".oosmanager");
+
+#include <algorithm>
+#include <vespa/fnet/frt/frt.h>
+#include "oosmanager.h"
+#include "rpcnetwork.h"
+
+namespace mbus {
+
+OOSClient::SP
+OOSManager::getClient(const string &spec)
+{
+ for (uint32_t i = 0; i < _clients.size(); ++i) {
+ if (_clients[i]->getSpec() == spec) {
+ return _clients[i];
+ }
+ }
+ return OOSClient::SP(new OOSClient(_orb, spec));
+}
+
+void
+OOSManager::PerformTask()
+{
+ bool changed = false;
+ if (_slobrokGen != _mirror.updates()) {
+ _slobrokGen = _mirror.updates();
+ SpecList newServices = _mirror.lookup(_servicePattern);
+ std::sort(newServices.begin(), newServices.end());
+ if (newServices != _services) {
+ ClientList newClients;
+ for (uint32_t i = 0; i < newServices.size(); ++i) {
+ newClients.push_back(getClient(newServices[i].second));
+ }
+ _services.swap(newServices);
+ _clients.swap(newClients);
+ changed = true;
+ }
+ }
+ bool allOk = _mirror.ready();
+ for (uint32_t i = 0; i < _clients.size(); ++i) {
+ if (_clients[i]->isChanged()) {
+ changed = true;
+ }
+ if (!_clients[i]->isReady()) {
+ allOk = false;
+ }
+ }
+ if (changed) {
+ OOSSet oos(new StringSet());
+ for (uint32_t i = 0; i < _clients.size(); ++i) {
+ _clients[i]->dumpState(*oos);
+ }
+ vespalib::LockGuard guard(_lock);
+ _oosSet.swap(oos);
+ }
+ if (allOk && !_ready) {
+ _ready = true;
+ }
+ Schedule(_ready ? 1.0 : 0.1);
+}
+
+OOSManager::OOSManager(FRT_Supervisor &orb,
+ slobrok::api::MirrorAPI &mirror,
+ const string &servicePattern)
+ : FNET_Task(orb.GetScheduler()),
+ _orb(orb),
+ _mirror(mirror),
+ _disabled(servicePattern.empty()),
+ _ready(_disabled),
+ _lock("mbus::OOSManager::_lock", false),
+ _servicePattern(servicePattern),
+ _slobrokGen(0),
+ _clients(),
+ _oosSet()
+{
+ if (!_disabled) {
+ ScheduleNow();
+ }
+}
+
+OOSManager::~OOSManager()
+{
+ Kill();
+}
+
+bool
+OOSManager::isOOS(const string &service)
+{
+ if (_disabled) {
+ return false;
+ }
+ vespalib::LockGuard guard(_lock);
+ if (_oosSet.get() == NULL) {
+ return false;
+ }
+ if (_oosSet->find(service) == _oosSet->end()) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/oosmanager.h b/messagebus/src/vespa/messagebus/network/oosmanager.h
new file mode 100644
index 00000000000..15a6fc48623
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/oosmanager.h
@@ -0,0 +1,93 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/fnet/fnet.h>
+#include <set>
+#include <vespa/slobrok/sbmirror.h>
+#include <string>
+#include <vespa/vespalib/util/sync.h>
+#include "oosclient.h"
+
+namespace mbus {
+
+class RPCNetwork;
+
+/**
+ * This class keeps track of OOS information. A set of servers having OOS information are identified by looking up a
+ * service pattern in the slobrok. These servers are then polled for information. The information is compiled into a
+ * local repository for fast lookup.
+ */
+class OOSManager : public boost::noncopyable, public FNET_Task {
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef slobrok::api::MirrorAPI MirrorAPI;
+ typedef MirrorAPI::SpecList SpecList;
+ typedef std::vector<OOSClient::SP> ClientList;
+ typedef std::set<string> StringSet;
+ typedef std::shared_ptr<StringSet> OOSSet;
+
+private:
+ FRT_Supervisor &_orb;
+ MirrorAPI &_mirror;
+ bool _disabled;
+ bool _ready;
+ vespalib::Lock _lock;
+ string _servicePattern;
+ uint32_t _slobrokGen;
+ SpecList _services;
+ ClientList _clients;
+ OOSSet _oosSet;
+
+ /**
+ * Reuse or create a client against the given server.
+ *
+ * @param spec The connection spec of the OOS server we want to talk to.
+ * @return A shared oosclient object.
+ */
+ OOSClient::SP getClient(const string &spec);
+
+ /**
+ * Method invoked when this object is run as a task. This method will update the oos information held by
+ * this object.
+ */
+ void PerformTask();
+
+public:
+ /**
+ * Create a new OOSManager. The given service pattern will be looked up in the given slobrok mirror. The
+ * resulting set of services will be polled for oos information.
+ *
+ * @param orb The supervisor used for RPC operations.
+ * @param mirror The slobrok mirror.
+ * @param servicePattern The service pattern for oos servers.
+ */
+ OOSManager(FRT_Supervisor &orb,
+ slobrok::api::MirrorAPI &mirror,
+ const string &servicePattern);
+
+ /**
+ * Destructor.
+ */
+ virtual ~OOSManager();
+
+ /**
+ * Returns whether or not some initial state has been returned.
+ *
+ * @return True, if initial state has been found.
+ */
+ bool isReady() const { return _ready; }
+
+ /**
+ * Returns whether or not the given service has been marked as out of service.
+ *
+ * @param service The service to check.
+ * @return True if the service is out of service.
+ */
+ bool isOOS(const string &service);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcnetwork.cpp b/messagebus/src/vespa/messagebus/network/rpcnetwork.cpp
new file mode 100644
index 00000000000..434cdb0e3c3
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcnetwork.cpp
@@ -0,0 +1,381 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".rpcnetwork");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/iprotocol.h>
+#include <vespa/messagebus/tracelevel.h>
+#include <vespa/messagebus/vtag.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <vespa/vespalib/util/sync.h>
+#include "inetworkowner.h"
+#include "rpcnetwork.h"
+#include "rpcsendv1.h"
+#include "rpcservice.h"
+
+namespace {
+
+/**
+ * Implements a helper class for {@link RPCNetwork#sync()}. It provides a
+ * blocking method {@link #await()} that will wait until the internal state
+ * of this object is set to 'done'. By scheduling this task in the network
+ * thread and then calling this method, we achieve handshaking with the network
+ * thread.
+ */
+class SyncTask : public FNET_Task {
+private:
+ vespalib::Gate _gate;
+
+public:
+ SyncTask(FNET_Scheduler &s) :
+ FNET_Task(&s),
+ _gate() {
+ ScheduleNow();
+ }
+
+ void await() {
+ _gate.await();
+ }
+
+ void PerformTask() {
+ _gate.countDown();
+ }
+};
+
+} // namespace <unnamed>
+
+namespace mbus {
+
+RPCNetwork::SendContext::SendContext(RPCNetwork &net, const Message &msg,
+ const std::vector<RoutingNode*> &recipients) :
+ _net(net),
+ _msg(msg),
+ _traceLevel(msg.getTrace().getLevel()),
+ _recipients(recipients),
+ _hasError(false),
+ _pending(_recipients.size()),
+ _version(_net.getVersion())
+{
+ // empty
+}
+
+void
+RPCNetwork::SendContext::handleVersion(const vespalib::Version *version)
+{
+ bool shouldSend = false;
+ {
+ vespalib::LockGuard guard(_lock);
+ if (version == NULL) {
+ _hasError = true;
+ } else if (*version < _version) {
+ _version = *version;
+ }
+ if (--_pending == 0) {
+ shouldSend = true;
+ }
+ }
+ if (shouldSend) {
+ _net.send(*this);
+ delete this;
+ }
+}
+
+RPCNetwork::TargetPoolTask::TargetPoolTask(
+ FNET_Scheduler &scheduler,
+ RPCTargetPool &pool) :
+ FNET_Task(&scheduler),
+ _pool(pool)
+{
+ ScheduleNow();
+}
+
+void
+RPCNetwork::TargetPoolTask::PerformTask()
+{
+ _pool.flushTargets(false);
+ Schedule(1.0);
+}
+
+RPCNetwork::RPCNetwork(const RPCNetworkParams &params) :
+ _owner(0),
+ _ident(params.getIdentity()),
+ _threadPool(128000, 0),
+ _transport(),
+ _orb(&_transport, NULL),
+ _scheduler(*_transport.GetScheduler()),
+ _targetPool(params.getConnectionExpireSecs()),
+ _targetPoolTask(_scheduler, _targetPool),
+ _servicePool(*this, 4096),
+ _mirror(_orb, slobrok::ConfiguratorFactory(params.getSlobrokConfig())),
+ _regAPI(_orb, slobrok::ConfiguratorFactory(params.getSlobrokConfig())),
+ _oosManager(_orb, _mirror, params.getOOSServerPattern()),
+ _requestedPort(params.getListenPort()),
+ _sendV1(),
+ _sendAdapters()
+{
+ _transport.SetDirectWrite(false);
+ _transport.SetMaxInputBufferSize(params.getMaxInputBufferSize());
+ _transport.SetMaxOutputBufferSize(params.getMaxOutputBufferSize());
+}
+
+RPCNetwork::~RPCNetwork()
+{
+ shutdown();
+}
+
+FRT_RPCRequest *
+RPCNetwork::allocRequest()
+{
+ return _orb.AllocRPCRequest();
+}
+
+RPCTarget::SP
+RPCNetwork::getTarget(const RPCServiceAddress &address)
+{
+ return _targetPool.getTarget(_orb, address);
+}
+
+void
+RPCNetwork::replyError(const SendContext &ctx, uint32_t errCode,
+ const string &errMsg)
+{
+ for (std::vector<RoutingNode*>::const_iterator it = ctx._recipients.begin();
+ it != ctx._recipients.end(); ++it)
+ {
+ Reply::UP reply(new EmptyReply());
+ reply->setTrace(Trace(ctx._traceLevel));
+ reply->addError(Error(errCode, errMsg));
+ _owner->deliverReply(std::move(reply), **it);
+ }
+}
+
+void
+RPCNetwork::flushTargetPool()
+{
+ _targetPool.flushTargets(true);
+}
+
+const vespalib::Version &
+RPCNetwork::getVersion() const
+{
+ return Vtag::currentVersion;
+}
+
+void
+RPCNetwork::attach(INetworkOwner &owner)
+{
+ LOG_ASSERT(_owner == 0);
+ _owner = &owner;
+
+ _sendV1.attach(*this);
+ _sendAdapters.insert(SendAdapterMap::value_type(vespalib::VersionSpecification(5), &_sendV1));
+ _sendAdapters.insert(SendAdapterMap::value_type(vespalib::VersionSpecification(6), &_sendV1));
+
+ FRT_ReflectionBuilder builder(&_orb);
+ builder.DefineMethod("mbus.getVersion", "", "s", true,
+ FRT_METHOD(RPCNetwork::invoke), this);
+ builder.MethodDesc("Retrieves the message bus version.");
+ builder.ReturnDesc("version", "The message bus version.");
+}
+
+void
+RPCNetwork::invoke(FRT_RPCRequest *req)
+{
+ req->GetReturn()->AddString(getVersion().toString().c_str());
+}
+
+const string
+RPCNetwork::getConnectionSpec() const
+{
+ return vespalib::make_vespa_string("tcp/%s:%d", _ident.getHostname().c_str(), _orb.GetListenPort());
+}
+
+RPCSendAdapter *
+RPCNetwork::getSendAdapter(const vespalib::Version &version)
+{
+ for (SendAdapterMap::iterator it = _sendAdapters.begin();
+ it != _sendAdapters.end(); ++it)
+ {
+ if (it->first.matches(version)) {
+ return it->second;
+ }
+ }
+ return NULL;
+}
+
+bool
+RPCNetwork::start()
+{
+ if (!_orb.Listen(_requestedPort)) {
+ return false;
+ }
+ if (!_transport.Start(&_threadPool)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+RPCNetwork::waitUntilReady(double seconds) const
+{
+ for (uint32_t i = 0; i < seconds * 100; ++i) {
+ if (_mirror.ready() && _oosManager.isReady()) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ return false;
+}
+
+void
+RPCNetwork::registerSession(const string &session)
+{
+ if (_ident.getServicePrefix().size() == 0) {
+ LOG(warning, "The session (%s) will not be registered"
+ "in the Slobrok since this network has no identity.",
+ session.c_str());
+ return;
+ }
+ string name = _ident.getServicePrefix();
+ name += "/";
+ name += session;
+ _regAPI.registerName(name);
+}
+
+void
+RPCNetwork::unregisterSession(const string &session)
+{
+ if (_ident.getServicePrefix().size() == 0) {
+ return;
+ }
+ string name = _ident.getServicePrefix();
+ name += "/";
+ name += session;
+ _regAPI.unregisterName(name);
+}
+
+bool
+RPCNetwork::allocServiceAddress(RoutingNode &recipient)
+{
+ const Hop &hop = recipient.getRoute().getHop(0);
+ string service = hop.getServiceName();
+ Error error = resolveServiceAddress(recipient, service);
+ if (error.getCode() == ErrorCode::NONE) {
+ return true; // service address resolved
+ }
+ recipient.setError(error);
+ return false; // service adddress not resolved
+}
+
+Error
+RPCNetwork::resolveServiceAddress(RoutingNode &recipient, const string &serviceName)
+{
+ if (_oosManager.isOOS(serviceName)) {
+ return Error(ErrorCode::SERVICE_OOS,
+ vespalib::make_vespa_string("The service '%s' has been marked as out of service.",
+ serviceName.c_str()));
+ }
+ RPCServiceAddress::UP ret = _servicePool.resolve(serviceName);
+ if (ret.get() == NULL) {
+ return Error(ErrorCode::NO_ADDRESS_FOR_SERVICE,
+ vespalib::make_vespa_string("The address of service '%s' could not be resolved. It is not currently "
+ "registered with the Vespa name server. "
+ "The service must be having problems, or the routing configuration is wrong.",
+ serviceName.c_str()));
+ }
+ RPCTarget::SP target = _targetPool.getTarget(_orb, *ret);
+ if (target.get() == NULL) {
+ return Error(ErrorCode::CONNECTION_ERROR,
+ vespalib::make_vespa_string("Failed to connect to service '%s'.",
+ serviceName.c_str()));
+ }
+ ret->setTarget(target); // free by freeServiceAddress()
+ recipient.setServiceAddress(IServiceAddress::UP(ret.release()));
+ return Error();
+}
+
+void
+RPCNetwork::freeServiceAddress(RoutingNode &recipient)
+{
+ recipient.setServiceAddress(IServiceAddress::UP());
+}
+
+void
+RPCNetwork::send(const Message &msg, const std::vector<RoutingNode*> &recipients)
+{
+ SendContext &ctx = *(new SendContext(*this, msg, recipients)); // deletes self
+ double timeout = ctx._msg.getTimeRemainingNow() / 1000.0;
+ for (uint32_t i = 0, len = ctx._recipients.size(); i < len; ++i) {
+ RoutingNode *&recipient = ctx._recipients[i];
+ LOG_ASSERT(recipient != NULL);
+
+ RPCServiceAddress &address = static_cast<RPCServiceAddress&>(recipient->getServiceAddress());
+ LOG_ASSERT(address.hasTarget());
+
+ address.getTarget().resolveVersion(timeout, ctx);
+ }
+}
+
+void
+RPCNetwork::send(RPCNetwork::SendContext &ctx)
+{
+ if (ctx._hasError) {
+ replyError(ctx, ErrorCode::HANDSHAKE_FAILED,
+ "An error occured while resolving version.");
+ } else {
+ uint64_t timeRemaining = ctx._msg.getTimeRemainingNow();
+ Blob payload = _owner->getProtocol(ctx._msg.getProtocol())->encode(ctx._version, ctx._msg);
+ RPCSendAdapter *adapter = getSendAdapter(ctx._version);
+ if (adapter == NULL) {
+ replyError(ctx, ErrorCode::INCOMPATIBLE_VERSION,
+ vespalib::make_vespa_string(
+ "Can not send to version '%s' recipient.",
+ ctx._version.toString().c_str()));
+ } else if (timeRemaining == 0) {
+ replyError(ctx, ErrorCode::TIMEOUT,
+ "Aborting transmission because zero time remains.");
+ } else if (payload.size() == 0) {
+ replyError(ctx, ErrorCode::ENCODE_ERROR,
+ vespalib::make_vespa_string(
+ "Protocol '%s' failed to encode message.",
+ ctx._msg.getProtocol().c_str()));
+ } else if (ctx._recipients.size() == 1) {
+ adapter->sendByHandover(*ctx._recipients.front(), ctx._version, std::move(payload), timeRemaining);
+ } else {
+ for (auto & recipient : ctx._recipients) {
+ adapter->send(*recipient, ctx._version, payload, timeRemaining);
+ }
+ }
+ }
+}
+
+void
+RPCNetwork::sync()
+{
+ SyncTask task(_scheduler);
+ task.await();
+}
+
+void
+RPCNetwork::shutdown()
+{
+ _transport.ShutDown(false);
+ _threadPool.Close();
+}
+
+void
+RPCNetwork::postShutdownHook()
+{
+ _scheduler.CheckTasks();
+}
+
+const slobrok::api::IMirrorAPI &
+RPCNetwork::getMirror() const
+{
+ return _mirror;
+}
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcnetwork.h b/messagebus/src/vespa/messagebus/network/rpcnetwork.h
new file mode 100644
index 00000000000..5585ff06cec
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcnetwork.h
@@ -0,0 +1,253 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/messagebus/blob.h>
+#include <vespa/messagebus/blobref.h>
+#include <vespa/messagebus/message.h>
+#include <vespa/messagebus/reply.h>
+#include <vespa/slobrok/sbmirror.h>
+#include <vespa/slobrok/sbregister.h>
+#include <vespa/vespalib/component/versionspecification.h>
+#include "inetwork.h"
+#include "oosmanager.h"
+#include "rpcnetworkparams.h"
+#include "rpcsendv1.h"
+#include "rpcservicepool.h"
+#include "rpctargetpool.h"
+
+namespace mbus {
+
+/**
+ * Network implementation based on RPC. This class is responsible for
+ * keeping track of services and for sending messages to services.
+ **/
+class RPCNetwork : public boost::noncopyable,
+ public INetwork,
+ public FRT_Invokable {
+private:
+ struct SendContext : public RPCTarget::IVersionHandler {
+ vespalib::Lock _lock;
+ RPCNetwork &_net;
+ const Message &_msg;
+ uint32_t _traceLevel;
+ std::vector<RoutingNode*> _recipients;
+ bool _hasError;
+ uint32_t _pending;
+ vespalib::Version _version;
+
+ SendContext(RPCNetwork &net, const Message &msg, const std::vector<RoutingNode*> &recipients);
+ void handleVersion(const vespalib::Version *version);
+ };
+
+ struct TargetPoolTask : public FNET_Task {
+ RPCTargetPool &_pool;
+
+ TargetPoolTask(FNET_Scheduler &scheduler, RPCTargetPool &pool);
+ void PerformTask();
+ };
+
+ typedef std::map<vespalib::VersionSpecification, RPCSendAdapter*> SendAdapterMap;
+
+ INetworkOwner *_owner;
+ Identity _ident;
+ FastOS_ThreadPool _threadPool;
+ FNET_Transport _transport;
+ FRT_Supervisor _orb;
+ FNET_Scheduler &_scheduler;
+ RPCTargetPool _targetPool;
+ TargetPoolTask _targetPoolTask;
+ RPCServicePool _servicePool;
+ slobrok::api::MirrorAPI _mirror;
+ slobrok::api::RegisterAPI _regAPI;
+ OOSManager _oosManager;
+ int _requestedPort;
+ RPCSendV1 _sendV1;
+ SendAdapterMap _sendAdapters;
+
+ /**
+ * Resolves and assigns a service address for the given recipient using the
+ * given address. This is called by the {@link
+ * #allocServiceAddress(RoutingNode)} method. The target allocated here is
+ * released when the routing node calls {@link
+ * #freeServiceAddress(RoutingNode)}.
+ *
+ * @param recipient The recipient to assign the service address to.
+ * @param serviceName The name of the service to resolve.
+ * @return Any error encountered, or ErrorCode::NONE.
+ */
+ Error resolveServiceAddress(RoutingNode &recipient, const string &serviceName);
+
+ /**
+ * Determines and returns the send adapter that is compatible with the given
+ * version. If no adapter can be found, this method returns null.
+ *
+ * @param version The version for which to return an adapter.
+ * @return The compatible adapter.
+ */
+ RPCSendAdapter *getSendAdapter(const vespalib::Version &version);
+
+ /**
+ * This method is a callback invoked after {@link #send(Message, List)} once
+ * the version of all recipients have been resolved. If all versions were
+ * resolved ahead of time, this method is invoked by the same thread as the
+ * former. If not, this method is invoked by the network thread during the
+ * version callback.
+ *
+ * @param ctx All the required send-data.
+ */
+ void send(SendContext &ctx);
+
+protected:
+ /**
+ * Returns the version of this network. This gets called when the
+ * "mbus.getVersion" method is invoked on this network, and is separated
+ * into its own function so that unit tests can override it to simulate
+ * other versions than current.
+ *
+ * @return The version to claim to be.
+ */
+ virtual const vespalib::Version &getVersion() const;
+
+ /**
+ * The network uses a cache of RPC targets (see {@link RPCTargetPool}) that
+ * allows it to save time by reusing open connections. It works by keeping a
+ * set of the most recently used targets open. Calling this method forces
+ * all unused connections to close immediately.
+ */
+ void flushTargetPool();
+
+public:
+ /**
+ * Create an RPCNetwork. The servicePrefix is combined with session names to
+ * create service names. If the service prefix is 'a/b' and the session name
+ * is 'c', the resulting service name that identifies the session on the
+ * message bus will be 'a/b/c'
+ *
+ * @param params A complete set of parameters.
+ */
+ RPCNetwork(const RPCNetworkParams &params);
+
+ /**
+ * Destruct
+ **/
+ virtual ~RPCNetwork();
+
+ /**
+ * Obtain the owner of this network. This method may only be invoked after
+ * the network has been attached to its owner.
+ *
+ * @return network owner
+ **/
+ INetworkOwner &getOwner() { return *_owner; }
+
+ /**
+ * Returns the identity of this network.
+ *
+ * @return The identity.
+ */
+ const Identity &getIdentity() const { return _ident; }
+
+ /**
+ * Obtain the port number this network is listening to. This method will
+ * return 0 until the start method has been invoked.
+ *
+ * @return port number
+ **/
+ int getPort() const { return _orb.GetListenPort(); }
+
+ /**
+ * Allocate a new rpc request object. The caller of this method gets the
+ * ownership of the returned request.
+ *
+ * @return a new rpc request
+ **/
+ FRT_RPCRequest *allocRequest();
+
+ /**
+ * Returns an RPC target for the given service address.
+ *
+ * @param address The address for which to return a target.
+ * @return The target to send to.
+ */
+ RPCTarget::SP getTarget(const RPCServiceAddress &address);
+
+ /**
+ * Obtain a reference to the internal scheduler. This will be mostly used
+ * for testing.
+ *
+ * @return internal scheduler
+ **/
+ FNET_Scheduler &getScheduler() { return _scheduler; }
+
+ /**
+ * Obtain a reference to the internal OOS manager object. This will be
+ * mostly used for testing.
+ *
+ * @return internal OOS manager
+ **/
+ OOSManager &getOOSManager() { return _oosManager; }
+
+ /**
+ * Obtain a reference to the internal supervisor. This is used by
+ * the request adapters to register FRT methods.
+ *
+ * @return The supervisor.
+ */
+ FRT_Supervisor &getSupervisor() { return _orb; }
+
+ /**
+ * Deliver an error reply to the recipients of a {@link SendContext} in a
+ * way that avoids entanglement.
+ *
+ * @param ctx The send context that contains the recipient data.
+ * @param errCode The error code to return.
+ * @param errMsg The error string to return.
+ */
+ void replyError(const SendContext &ctx, uint32_t errCode,
+ const string &errMsg);
+
+ // Implements INetwork.
+ void attach(INetworkOwner &owner);
+
+ // Implements INetwork.
+ const string getConnectionSpec() const;
+
+ // Implements INetwork.
+ bool start();
+
+ // Implements INetwork.
+ bool waitUntilReady(double seconds) const;
+
+ // Implements INetwork.
+ void registerSession(const string &session);
+
+ // Implements INetwork.
+ void unregisterSession(const string &session);
+
+ // Implements INetwork.
+ bool allocServiceAddress(RoutingNode &recipient);
+
+ // Implements INetwork.
+ void freeServiceAddress(RoutingNode &recipient);
+
+ // Implements INetwork.
+ void send(const Message &msg, const std::vector<RoutingNode*> &recipients);
+
+ // Implements INetwork.
+ void sync();
+
+ // Implements INetwork.
+ void shutdown();
+
+ // Implements INetwork.
+ void postShutdownHook();
+
+ // Implements INetwork.
+ const slobrok::api::IMirrorAPI &getMirror() const;
+
+ // Implements FRT_Invokable.
+ void invoke(FRT_RPCRequest *req);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp
new file mode 100644
index 00000000000..fe0327a5c6e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".rpcnetworkparams");
+
+#include "rpcnetworkparams.h"
+
+#include <vespa/slobrok/cfg.h>
+
+namespace mbus {
+
+RPCNetworkParams::RPCNetworkParams() :
+ _identity(Identity("")),
+ _slobrokConfig("admin/slobrok.0"),
+ _oosServerPattern(""),
+ _listenPort(0),
+ _maxInputBufferSize(256*1024),
+ _maxOutputBufferSize(256*1024),
+ _connectionExpireSecs(30)
+{
+ // empty
+}
+
+}
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcnetworkparams.h b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.h
new file mode 100644
index 00000000000..8d9d4066177
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.h
@@ -0,0 +1,186 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <stdint.h>
+#include "identity.h"
+#include <vespa/slobrok/cfg.h>
+
+namespace mbus {
+
+/**
+ * To facilitate several configuration parameters to the {@link RPCNetwork} constructor, all parameters are
+ * held by this class. This class has reasonable default values for each parameter.
+ */
+class RPCNetworkParams {
+private:
+ Identity _identity;
+ config::ConfigUri _slobrokConfig;
+ string _oosServerPattern;
+ int _listenPort;
+ uint32_t _maxInputBufferSize;
+ uint32_t _maxOutputBufferSize;
+ double _connectionExpireSecs;
+
+public:
+ /**
+ * Constructs a new parameter object with default values.
+ */
+ RPCNetworkParams();
+
+ /**
+ * Returns the identity to use for the network.
+ *
+ * @return The identity.
+ */
+ const Identity &getIdentity() const {
+ return _identity;
+ }
+
+ /**
+ * Sets the identity to use for the network.
+ *
+ * @param identity The new identity.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setIdentity(const Identity &identity) {
+ _identity = identity;
+ return *this;
+ }
+
+ /**
+ * Sets the identity to use for the network.
+ *
+ * @param identity A string representation of the identity.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setIdentity(const string &identity) {
+ return setIdentity(Identity(identity));
+ }
+
+ /**
+ * Returns the config id of the slobrok config.
+ *
+ * @return The config id.
+ */
+ const config::ConfigUri & getSlobrokConfig() const {
+ return _slobrokConfig;
+ }
+
+ /**
+ * Sets of the slobrok config.
+ *
+ * @param slobrokConfigId The new config.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setSlobrokConfig(const config::ConfigUri & slobrokConfig) {
+ _slobrokConfig = slobrokConfig;
+ return *this;
+ }
+
+ /**
+ * Returns the config id pattern used to lookup OOS servers.
+ *
+ * @return The config id.
+ */
+ const string &getOOSServerPattern() const {
+ return _oosServerPattern;
+ }
+
+ /**
+ * Sets the config id pattern used to lookup OOS servers.
+ *
+ * @param oosServerPattern The server pattern.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setOOSServerPattern(const string &oosServerPattern) {
+ _oosServerPattern = oosServerPattern;
+ return *this;
+ }
+
+ /**
+ * Returns the port to listen to.
+ *
+ * @return The port.
+ */
+ int getListenPort() const {
+ return _listenPort;
+ }
+
+ /**
+ * Sets the port to listen to.
+ *
+ * @param listenPort The new port.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setListenPort(int listenPort) {
+ _listenPort = listenPort;
+ return *this;
+ }
+
+ /**
+ * Returns the number of seconds before an idle network connection expires.
+ *
+ * @return The number of seconds.
+ */
+ double getConnectionExpireSecs() const{
+ return _connectionExpireSecs;
+ }
+
+ /**
+ * Sets the number of seconds before an idle network connection expires.
+ *
+ * @param secs The number of seconds.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setConnectionExpireSecs(double secs) {
+ _connectionExpireSecs = secs;
+ return *this;
+ }
+
+ /**
+ * Returns the maximum input buffer size allowed for the underlying FNET connection.
+ *
+ * @return The maximum number of bytes.
+ */
+ uint32_t getMaxInputBufferSize() const {
+ return _maxInputBufferSize;
+ }
+
+ /**
+ * Sets the maximum input buffer size allowed for the underlying FNET connection. Using the value 0 means that there
+ * is no limit; the connection will not free any allocated memory until it is cleaned up. This might potentially
+ * save alot of allocation time.
+ *
+ * @param maxInputBufferSize The maximum number of bytes.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setMaxInputBufferSize(uint32_t maxInputBufferSize) {
+ _maxInputBufferSize = maxInputBufferSize;
+ return *this;
+ }
+
+ /**
+ * Returns the maximum output buffer size allowed for the underlying FNET connection.
+ *
+ * @return The maximum number of bytes.
+ */
+ uint32_t getMaxOutputBufferSize() const {
+ return _maxOutputBufferSize;
+ }
+
+ /**
+ * Sets the maximum output buffer size allowed for the underlying FNET connection. Using the value 0 means that there
+ * is no limit; the connection will not free any allocated memory until it is cleaned up. This might potentially
+ * save alot of allocation time.
+ *
+ * @param maxOutputBufferSize The maximum number of bytes.
+ * @return This, to allow chaining.
+ */
+ RPCNetworkParams &setMaxOutputBufferSize(uint32_t maxOutputBufferSize) {
+ _maxOutputBufferSize = maxOutputBufferSize;
+ return *this;
+ }
+};
+
+}
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcsendadapter.h b/messagebus/src/vespa/messagebus/network/rpcsendadapter.h
new file mode 100644
index 00000000000..91ba5de24b7
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcsendadapter.h
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <memory>
+#include <vespa/vespalib/util/referencecounter.h>
+
+namespace mbus {
+
+class RoutingNode;
+class RPCNetwork;
+
+/**
+ * This interface defines the necessary methods to process incoming and send
+ * outgoing RPC sends. The {@link RPCNetwork} maintains a list of supported RPC
+ * signatures, and dispatches sends to the corresponding adapter.
+ */
+class RPCSendAdapter : public boost::noncopyable
+{
+public:
+ /**
+ * Required for inheritance.
+ */
+ virtual ~RPCSendAdapter() { }
+
+ /**
+ * Attaches this adapter to the given network.
+ *
+ * @param net The network to attach to.
+ */
+ virtual void attach(RPCNetwork &net) = 0;
+
+ /**
+ * Performs the actual sending to the given recipient.
+ *
+ * @param recipient The recipient to send to.
+ * @param version The version for which the payload is serialized.
+ * @param payload The already serialized payload of the message to send.
+ * @param timeRemaining The time remaining until the message expires.
+ */
+ virtual void send(RoutingNode &recipient, const vespalib::Version &version,
+ BlobRef payload, uint64_t timeRemaining) = 0;
+
+ /**
+ * Performs the actual sending to the given recipient.
+ *
+ * @param recipient The recipient to send to.
+ * @param version The version for which the payload is serialized.
+ * @param payload The already serialized payload of the message to send.
+ * @param timeRemaining The time remaining until the message expires.
+ */
+ virtual void sendByHandover(RoutingNode &recipient, const vespalib::Version &version,
+ Blob payload, uint64_t timeRemaining) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcsendv1.cpp b/messagebus/src/vespa/messagebus/network/rpcsendv1.cpp
new file mode 100644
index 00000000000..642e8c64089
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcsendv1.cpp
@@ -0,0 +1,410 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".rpcsendv1");
+
+#include <vespa/messagebus/routing/routingnode.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/tracelevel.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "rpcnetwork.h"
+#include "rpcsendv1.h"
+#include "rpctarget.h"
+
+namespace {
+
+/**
+ * Implements a helper class to hold the necessary context to create a reply from
+ * an rpc return value. This object is held as the context of an FRT_RPCRequest.
+ */
+class SendContext : public boost::noncopyable {
+private:
+ mbus::RoutingNode &_recipient;
+ mbus::Trace _trace;
+ double _timeout;
+
+public:
+ typedef std::unique_ptr<SendContext> UP;
+
+ SendContext(mbus::RoutingNode &recipient, uint64_t timeRemaining)
+ : _recipient(recipient),
+ _trace(recipient.getTrace().getLevel()),
+ _timeout(timeRemaining * 0.001) { }
+ mbus::RoutingNode &getRecipient() { return _recipient; }
+ mbus::Trace &getTrace() { return _trace; }
+ double getTimeout() { return _timeout; }
+};
+
+/**
+ * Implements a helper class to hold the necessary context to send a reply as an
+ * rpc return value. This object is held in the callstack of the reply.
+ */
+class ReplyContext : public boost::noncopyable {
+private:
+ FRT_RPCRequest &_request;
+ vespalib::Version _version;
+
+public:
+ typedef std::unique_ptr<ReplyContext> UP;
+
+ ReplyContext(FRT_RPCRequest &request, const vespalib::Version &version)
+ : _request(request), _version(version) { }
+ FRT_RPCRequest &getRequest() { return _request; }
+ const vespalib::Version &getVersion() { return _version; }
+};
+
+}
+
+namespace mbus {
+
+const char *RPCSendV1::METHOD_NAME = "mbus.send1";
+const char *RPCSendV1::METHOD_PARAMS = "sssbilsxi";
+const char *RPCSendV1::METHOD_RETURN = "sdISSsxs";
+
+RPCSendV1::RPCSendV1() :
+ _net(NULL),
+ _clientIdent("client"),
+ _serverIdent("server")
+{
+ // empty
+}
+
+void
+RPCSendV1::attach(RPCNetwork &net)
+{
+ _net = &net;
+ const string &prefix = _net->getIdentity().getServicePrefix();
+ if (!prefix.empty()) {
+ _clientIdent = vespalib::make_vespa_string("'%s'", prefix.c_str());
+ _serverIdent = _clientIdent;
+ }
+
+ FRT_ReflectionBuilder builder(&_net->getSupervisor());
+ builder.DefineMethod(METHOD_NAME, METHOD_PARAMS, METHOD_RETURN, true,
+ FRT_METHOD(RPCSendV1::invoke), this);
+ builder.MethodDesc("Send a message bus request and get a reply back.");
+ builder.ParamDesc("version", "The version of the message.");
+ builder.ParamDesc("route", "Names of additional hops to visit.");
+ builder.ParamDesc("session", "The local session that should receive this message.");
+ builder.ParamDesc("retryEnabled", "Whether or not this message can be resent.");
+ builder.ParamDesc("retry", "The number of times the sending of this message has been retried.");
+ builder.ParamDesc("timeRemaining", "The number of milliseconds until timeout.");
+ builder.ParamDesc("protocol", "The name of the protocol that knows how to decode this message.");
+ builder.ParamDesc("payload", "The protocol specific message payload.");
+ builder.ParamDesc("level", "The trace level of the message.");
+ builder.ReturnDesc("version", "The lowest version the message was serialized as.");
+ builder.ReturnDesc("retry", "The retry request of the reply.");
+ builder.ReturnDesc("errorCodes", "The reply error codes.");
+ builder.ReturnDesc("errorMessages", "The reply error messages.");
+ builder.ReturnDesc("errorServices", "The reply error service names.");
+ builder.ReturnDesc("protocol", "The name of the protocol that knows how to decode this reply.");
+ builder.ReturnDesc("payload", "The protocol specific reply payload.");
+ builder.ReturnDesc("trace", "A string representation of the trace.");
+}
+
+namespace {
+
+class FillByCopy : public PayLoadFiller
+{
+public:
+ FillByCopy(BlobRef payload) : _payload(payload) { }
+ void fill(FRT_Values & v) const override {
+ v.AddData(_payload.data(), _payload.size());
+ }
+private:
+ BlobRef _payload;
+};
+
+class FillByHandover : public PayLoadFiller
+{
+public:
+ FillByHandover(Blob payload) : _payload(std::move(payload)) { }
+ void fill(FRT_Values & v) const override {
+ v.AddData(std::move(_payload.payload()), _payload.size());
+ }
+private:
+ mutable Blob _payload;
+};
+
+}
+
+void
+RPCSendV1::send(RoutingNode &recipient, const vespalib::Version &version,
+ BlobRef payload, uint64_t timeRemaining)
+{
+ send(recipient, version, FillByCopy(payload), timeRemaining);
+}
+
+void
+RPCSendV1::sendByHandover(RoutingNode &recipient, const vespalib::Version &version,
+ Blob payload, uint64_t timeRemaining)
+{
+ send(recipient, version, FillByHandover(std::move(payload)), timeRemaining);
+}
+
+void
+RPCSendV1::send(RoutingNode &recipient, const vespalib::Version &version,
+ const PayLoadFiller & payload, uint64_t timeRemaining)
+{
+ SendContext::UP ctx(new SendContext(recipient, timeRemaining));
+ RPCServiceAddress &address = static_cast<RPCServiceAddress&>(recipient.getServiceAddress());
+ const Message &msg = recipient.getMessage();
+ Route route = recipient.getRoute();
+ Hop hop = route.removeHop(0);
+
+ FRT_RPCRequest *req = _net->allocRequest();
+ FRT_Values &args = *req->GetParams();
+ req->SetMethodName(METHOD_NAME);
+ args.AddString(version.toString().c_str());
+ args.AddString(route.toString().c_str());
+ args.AddString(address.getSessionName().c_str());
+ args.AddInt8(msg.getRetryEnabled() ? 1 : 0);
+ args.AddInt32(msg.getRetry());
+ args.AddInt64(timeRemaining);
+ args.AddString(msg.getProtocol().c_str());
+ payload.fill(args);
+ args.AddInt32(recipient.getTrace().getLevel());
+
+ if (ctx->getTrace().shouldTrace(TraceLevel::SEND_RECEIVE)) {
+ ctx->getTrace().trace(TraceLevel::SEND_RECEIVE,
+ vespalib::make_vespa_string(
+ "Sending message (version %s) from %s to '%s' with %.2f seconds timeout.",
+ version.toString().c_str(), _clientIdent.c_str(),
+ address.getServiceName().c_str(), ctx->getTimeout()));
+ }
+
+ if (hop.getIgnoreResult()) {
+ address.getTarget().getFRTTarget().InvokeVoid(req);
+ if (ctx->getTrace().shouldTrace(TraceLevel::SEND_RECEIVE)) {
+ ctx->getTrace().trace(TraceLevel::SEND_RECEIVE,
+ vespalib::make_vespa_string("Not waiting for a reply from '%s'.",
+ address.getServiceName().c_str()));
+ }
+ Reply::UP reply(new EmptyReply());
+ reply->getTrace().swap(ctx->getTrace());
+ _net->getOwner().deliverReply(std::move(reply), recipient);
+ } else {
+ SendContext *ptr = ctx.release();
+ req->SetContext(FNET_Context(ptr));
+ address.getTarget().getFRTTarget().InvokeAsync(req, ptr->getTimeout(), this);
+ }
+}
+
+void
+RPCSendV1::RequestDone(FRT_RPCRequest *req)
+{
+ SendContext::UP ctx(static_cast<SendContext*>(req->GetContext()._value.VOIDP));
+ const string &serviceName = static_cast<RPCServiceAddress&>(
+ ctx->getRecipient().getServiceAddress()).getServiceName();
+ Reply::UP reply;
+ Error error;
+ if (!req->CheckReturnTypes(METHOD_RETURN)) {
+ reply.reset(new EmptyReply());
+ switch (req->GetErrorCode()) {
+ case FRTE_RPC_TIMEOUT:
+ error = Error(ErrorCode::TIMEOUT,
+ vespalib::make_vespa_string("A timeout occured while waiting for '%s' (%g seconds expired); %s",
+ serviceName.c_str(), ctx->getTimeout(), req->GetErrorMessage()));
+ break;
+ case FRTE_RPC_CONNECTION:
+ error = Error(ErrorCode::CONNECTION_ERROR,
+ vespalib::make_vespa_string("A connection error occured for '%s'; %s",
+ serviceName.c_str(), req->GetErrorMessage()));
+ break;
+ default:
+ error = Error(ErrorCode::NETWORK_ERROR,
+ vespalib::make_vespa_string("A network error occured for '%s'; %s",
+ serviceName.c_str(), req->GetErrorMessage()));
+ }
+ } else {
+ FRT_Values &ret = *req->GetReturn();
+
+ vespalib::Version version = vespalib::Version(ret[0]._string._str);
+ double retryDelay = ret[1]._double;
+ uint32_t *errorCodes = ret[2]._int32_array._pt;
+ uint32_t errorCodesLen = ret[2]._int32_array._len;
+ FRT_StringValue *errorMessages = ret[3]._string_array._pt;
+ uint32_t errorMessagesLen = ret[3]._string_array._len;
+ FRT_StringValue *errorServices = ret[4]._string_array._pt;
+ uint32_t errorServicesLen = ret[4]._string_array._len;
+ const char *protocolName = ret[5]._string._str;
+ const char *payload = ret[6]._data._buf;
+ uint32_t payloadLen = ret[6]._data._len;
+ const char *trace = ret[7]._string._str;
+
+ if (payloadLen > 0) {
+ IProtocol::SP protocol = _net->getOwner().getProtocol(protocolName);
+ if (protocol.get() != NULL) {
+ Routable::UP routable = protocol->decode(version,
+ BlobRef(payload, payloadLen));
+ if (routable.get() != NULL) {
+ if (routable->isReply()) {
+ reply.reset(static_cast<Reply*>(routable.release()));
+ } else {
+ error = Error(ErrorCode::DECODE_ERROR,
+ "Payload decoded to a message when expecting a reply.");
+ }
+ } else {
+ error = Error(ErrorCode::DECODE_ERROR,
+ vespalib::make_vespa_string("Protocol '%s' failed to decode routable.",
+ protocolName));
+ }
+
+ } else {
+ error = Error(ErrorCode::UNKNOWN_PROTOCOL,
+ vespalib::make_vespa_string("Protocol '%s' is not known by %s.",
+ protocolName, _serverIdent.c_str()));
+ }
+ }
+ if (reply.get() == NULL) {
+ reply.reset(new EmptyReply());
+ }
+ reply->setRetryDelay(retryDelay);
+ for (uint32_t i = 0; i < errorCodesLen && i < errorMessagesLen && i < errorServicesLen; ++i) {
+ reply->addError(Error(errorCodes[i],
+ errorMessages[i]._str,
+ errorServices[i]._len > 0 ? errorServices[i]._str : serviceName.c_str()));
+ }
+ ctx->getTrace().getRoot().addChild(TraceNode::decode(trace));
+ }
+ if (ctx->getTrace().shouldTrace(TraceLevel::SEND_RECEIVE)) {
+ ctx->getTrace().trace(TraceLevel::SEND_RECEIVE,
+ vespalib::make_vespa_string("Reply (type %d) received at %s.",
+ reply->getType(), _clientIdent.c_str()));
+ }
+ reply->getTrace().swap(ctx->getTrace());
+ if (error.getCode() != ErrorCode::NONE) {
+ reply->addError(error);
+ }
+ _net->getOwner().deliverReply(std::move(reply), ctx->getRecipient());
+ req->SubRef();
+}
+
+void
+RPCSendV1::invoke(FRT_RPCRequest *req)
+{
+ req->Detach();
+
+ FRT_Values &args = *req->GetParams();
+ vespalib::Version version = vespalib::Version(args[0]._string._str);
+ const char *route = args[1]._string._str;
+ const char *session = args[2]._string._str;
+ bool retryEnabled = args[3]._intval8 != 0;
+ uint32_t retry = args[4]._intval32;
+ uint64_t timeRemaining = args[5]._intval64;
+ const char *protocolName = args[6]._string._str;
+ const char *payload = args[7]._data._buf;
+ uint32_t payloadLen = args[7]._data._len;
+ uint32_t traceLevel = args[8]._intval32;
+
+ IProtocol::SP protocol = _net->getOwner().getProtocol(protocolName);
+ if (protocol.get() == NULL) {
+ replyError(req, version, traceLevel,
+ Error(ErrorCode::UNKNOWN_PROTOCOL,
+ vespalib::make_vespa_string("Protocol '%s' is not known by %s.",
+ protocolName, _serverIdent.c_str())));
+ return;
+ }
+ Routable::UP routable = protocol->decode(version, BlobRef(payload, payloadLen));
+ req->DiscardBlobs();
+ if (routable.get() == NULL) {
+ replyError(req, version, traceLevel,
+ Error(ErrorCode::DECODE_ERROR,
+ vespalib::make_vespa_string("Protocol '%s' failed to decode routable.",
+ protocolName)));
+ return;
+ }
+ if (routable->isReply()) {
+ replyError(req, version, traceLevel,
+ Error(ErrorCode::DECODE_ERROR,
+ "Payload decoded to a reply when expecting a mesage."));
+ return;
+ }
+ Message::UP msg(static_cast<Message*>(routable.release()));
+ if (strlen(route) > 0) {
+ msg->setRoute(Route::parse(route));
+ }
+ msg->setContext(Context(new ReplyContext(*req, version)));
+ msg->pushHandler(*this, *this);
+ msg->setRetryEnabled(retryEnabled);
+ msg->setRetry(retry);
+ msg->setTimeReceivedNow();
+ msg->setTimeRemaining(timeRemaining);
+ msg->getTrace().setLevel(traceLevel);
+ if (msg->getTrace().shouldTrace(TraceLevel::SEND_RECEIVE)) {
+ msg->getTrace().trace(TraceLevel::SEND_RECEIVE,
+ vespalib::make_vespa_string("Message (type %d) received at %s for session '%s'.",
+ msg->getType(), _serverIdent.c_str(), session));
+ }
+ _net->getOwner().deliverMessage(std::move(msg), session);
+}
+
+void
+RPCSendV1::handleReply(Reply::UP reply)
+{
+ ReplyContext::UP ctx(static_cast<ReplyContext*>(reply->getContext().value.PTR));
+ FRT_RPCRequest &req = ctx->getRequest();
+ string version = ctx->getVersion().toString();
+ if (reply->getTrace().shouldTrace(TraceLevel::SEND_RECEIVE)) {
+ reply->getTrace().trace(TraceLevel::SEND_RECEIVE,
+ vespalib::make_vespa_string("Sending reply (version %s) from %s.",
+ version.c_str(), _serverIdent.c_str()));
+ }
+ Blob payload(0);
+ if (reply->getType() != 0) {
+ payload = _net->getOwner().getProtocol(reply->getProtocol())->encode(ctx->getVersion(), *reply);
+ if (payload.size() == 0) {
+ reply->addError(Error(ErrorCode::ENCODE_ERROR,
+ "An error occured while encoding the reply, see log."));
+ }
+ }
+ FRT_Values &ret = *req.GetReturn();
+ ret.AddString(version.c_str());
+ ret.AddDouble(reply->getRetryDelay());
+
+ uint32_t errorCount = reply->getNumErrors();
+ uint32_t *errorCodes = ret.AddInt32Array(errorCount);
+ FRT_StringValue *errorMessages = ret.AddStringArray(errorCount);
+ FRT_StringValue *errorServices = ret.AddStringArray(errorCount);
+ for (uint32_t i = 0; i < errorCount; ++i) {
+ errorCodes[i] = reply->getError(i).getCode();
+ ret.SetString(errorMessages + i,
+ reply->getError(i).getMessage().c_str());
+ ret.SetString(errorServices + i,
+ reply->getError(i).getService().c_str());
+ }
+
+ ret.AddString(reply->getProtocol().c_str());
+ ret.AddData(std::move(payload.payload()), payload.size());
+ if (reply->getTrace().getLevel() > 0) {
+ ret.AddString(reply->getTrace().getRoot().encode().c_str());
+ } else {
+ ret.AddString("");
+ }
+ req.Return();
+}
+
+void
+RPCSendV1::handleDiscard(Context ctx)
+{
+ ReplyContext::UP tmp(static_cast<ReplyContext*>(ctx.value.PTR));
+ FRT_RPCRequest &req = tmp->getRequest();
+ FNET_Channel *chn = req.GetContext()._value.CHANNEL;
+ req.SubRef();
+ chn->Free();
+}
+
+void
+RPCSendV1::replyError(FRT_RPCRequest *req, const vespalib::Version &version,
+ uint32_t traceLevel, const Error &err)
+{
+ Reply::UP reply(new EmptyReply());
+ reply->setContext(Context(new ReplyContext(*req, version)));
+ reply->getTrace().setLevel(traceLevel);
+ reply->addError(err);
+ handleReply(std::move(reply));
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/rpcsendv1.h b/messagebus/src/vespa/messagebus/network/rpcsendv1.h
new file mode 100644
index 00000000000..570b3daff82
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcsendv1.h
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/fnet/frt/frt.h>
+#include <vespa/messagebus/idiscardhandler.h>
+#include <vespa/messagebus/ireplyhandler.h>
+#include "rpcsendadapter.h"
+
+namespace mbus {
+
+class PayLoadFiller
+{
+public:
+ virtual ~PayLoadFiller() { }
+ virtual void fill(FRT_Values & v) const = 0;
+};
+
+/**
+ * Implements the send adapter for method "mbus.send".
+ */
+class RPCSendV1 : public RPCSendAdapter,
+ public FRT_Invokable,
+ public FRT_IRequestWait,
+ public IDiscardHandler,
+ public IReplyHandler {
+private:
+ RPCNetwork *_net;
+ string _clientIdent;
+ string _serverIdent;
+
+ /**
+ * Send an error reply for a given request.
+ *
+ * @param request The FRT request to reply to.
+ * @param version The version to serialize for.
+ * @param traceLevel The trace level to set in the reply.
+ * @param err The error to reply with.
+ */
+ void replyError(FRT_RPCRequest *req, const vespalib::Version &version,
+ uint32_t traceLevel, const Error &err);
+
+ void send(RoutingNode &recipient, const vespalib::Version &version,
+ const PayLoadFiller & filler, uint64_t timeRemaining);
+public:
+ /** The name of the rpc method that this adapter registers. */
+ static const char *METHOD_NAME;
+
+ /** The parameter string of the rpc method. */
+ static const char *METHOD_PARAMS;
+
+ /** The return string of the rpc method. */
+ static const char *METHOD_RETURN;
+
+ /**
+ * Constructs a new instance of this adapter. This object is unusable until
+ * its attach() method has been called.
+ */
+ RPCSendV1();
+
+ // Implements RPCSendAdapter.
+ void attach(RPCNetwork &net) override;
+
+ // Implements RPCSendAdapter.
+ void send(RoutingNode &recipient, const vespalib::Version &version,
+ BlobRef payload, uint64_t timeRemaining) override;
+ void sendByHandover(RoutingNode &recipient, const vespalib::Version &version,
+ Blob payload, uint64_t timeRemaining) override;
+
+ // Implements IReplyHandler.
+ void handleReply(Reply::UP reply) override;
+
+ // Implements IDiscardHandler.
+ void handleDiscard(Context ctx) override;
+
+ // Implements FRT_Invokable.
+ void invoke(FRT_RPCRequest *req);
+
+ // Implements FRT_IRequestWait.
+ void RequestDone(FRT_RPCRequest *req) override;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcservice.cpp b/messagebus/src/vespa/messagebus/network/rpcservice.cpp
new file mode 100644
index 00000000000..c420f817726
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcservice.cpp
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".rpcservice");
+
+#include "rpcservice.h"
+#include "rpcserviceaddress.h"
+#include "rpcnetwork.h"
+
+namespace mbus {
+
+RPCService::RPCService(const slobrok::api::IMirrorAPI &mirror,
+ const string &pattern) :
+ _mirror(mirror),
+ _pattern(pattern),
+ _addressIdx(random()),
+ _addressGen(0),
+ _addressList()
+{
+ // empty
+}
+
+RPCServiceAddress::UP
+RPCService::resolve()
+{
+ if (_pattern.find("tcp/") == 0) {
+ size_t pos = _pattern.find_last_of('/');
+ if (pos != string::npos && pos < _pattern.size() - 1) {
+ RPCServiceAddress::UP ret(new RPCServiceAddress(
+ _pattern,
+ _pattern.substr(0, pos)));
+ if (!ret->isMalformed()) {
+ return ret;
+ }
+ }
+ } else {
+ if (_addressGen != _mirror.updates()) {
+ _addressGen = _mirror.updates();
+ _addressList = _mirror.lookup(_pattern);
+ }
+ if (!_addressList.empty()) {
+ _addressIdx = (_addressIdx + 1) % _addressList.size();
+ const AddressList::value_type &entry = _addressList[_addressIdx];
+ return RPCServiceAddress::UP(new RPCServiceAddress(
+ entry.first,
+ entry.second));
+ }
+ }
+ return RPCServiceAddress::UP();
+}
+
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/rpcservice.h b/messagebus/src/vespa/messagebus/network/rpcservice.h
new file mode 100644
index 00000000000..95e2bfaa377
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcservice.h
@@ -0,0 +1,59 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/slobrok/sbmirror.h>
+#include <vespa/slobrok/sbregister.h>
+#include <vespa/vespalib/util/linkedptr.h>
+#include "rpcserviceaddress.h"
+
+namespace mbus {
+
+class RPCNetwork;
+
+/**
+ * An RPCService represents a set of remote sessions matching a service pattern.
+ * The sessions are monitored using the slobrok. If multiple sessions are
+ * available, round robin is used to balance load between them.
+ */
+class RPCService : public boost::noncopyable {
+private:
+ typedef slobrok::api::IMirrorAPI Mirror;
+ typedef slobrok::api::MirrorAPI::SpecList AddressList;
+
+ const Mirror &_mirror;
+ string _pattern;
+ uint32_t _addressIdx;
+ uint32_t _addressGen;
+ AddressList _addressList;
+
+public:
+ typedef vespalib::LinkedPtr<RPCService> LP;
+ /**
+ * Create a new RPCService backed by the given network and using
+ * the given service pattern.
+ *
+ * @param mirror The naming server to send queries to.
+ * @param pattern The pattern to use when querying.
+ */
+ RPCService(const slobrok::api::IMirrorAPI &mirror, const string &pattern);
+
+ /**
+ * Resolve a concrete address from this service. This service may represent
+ * multiple remote sessions, so this will select one that is online.
+ *
+ * @return A concrete service address.
+ */
+ RPCServiceAddress::UP resolve();
+
+ /**
+ * Returns the pattern used when querying for the naming server for
+ * addresses. This is given at construtor time.
+ *
+ * @return The service pattern.
+ */
+ const string &getPattern() const { return _pattern; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp
new file mode 100644
index 00000000000..bae53313852
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp
@@ -0,0 +1,52 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".rpcserviceaddress");
+
+#include "rpcserviceaddress.h"
+
+namespace mbus {
+
+RPCServiceAddress::RPCServiceAddress(const string &serviceName,
+ const string &connectionSpec) :
+ _serviceName(serviceName),
+ _sessionName(""),
+ _connectionSpec(connectionSpec),
+ _target()
+{
+ size_t pos = serviceName.find_last_of('/');
+ if (pos != string::npos) {
+ _sessionName = serviceName.substr(pos + 1);
+ }
+}
+
+bool
+RPCServiceAddress::isMalformed()
+{
+ if (_serviceName.empty()) {
+ return true; // no service
+ }
+ if (_sessionName.empty()) {
+ return true; // no session
+ }
+ if (_connectionSpec.empty()) {
+ return true; // no spec
+ }
+ size_t prefixPos = _connectionSpec.find("tcp/");
+ if (prefixPos != 0) {
+ return true; // no prefix
+ }
+ size_t portPos = _connectionSpec.find_first_of(':', prefixPos);
+ if (portPos == string::npos) {
+ return true; // not colon
+ }
+ if (portPos == prefixPos + 4) {
+ return true; // no address
+ }
+ if (portPos == _connectionSpec.size() - 1) {
+ return true; // no port
+ }
+ return false;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/rpcserviceaddress.h b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.h
new file mode 100644
index 00000000000..0a923eda0c1
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.h
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string>
+#include "iserviceaddress.h"
+#include "rpctarget.h"
+
+namespace mbus {
+
+/**
+ * An RPCServiceAddress contains the service name, connection spec and
+ * session name of a concrete remote rpc service.
+ **/
+class RPCServiceAddress : public IServiceAddress {
+private:
+ string _serviceName;
+ string _sessionName;
+ string _connectionSpec;
+ RPCTarget::SP _target;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<RPCServiceAddress> UP;
+
+ /**
+ * Constructs a service address from the given specifications. The last component of the service is stored
+ * as the session name.
+ *
+ * @param serviceName The full service name of the address.
+ * @param connectionSpec The connection specification.
+ */
+ RPCServiceAddress(const string &serviceName,
+ const string &connectionSpec);
+
+ /**
+ * Returns whether or not this service address is malformed.
+ *
+ * @return True if malformed.
+ */
+ bool isMalformed();
+
+ /**
+ * Returns the name of the remove service.
+ *
+ * @return The service name.
+ */
+ const string &getServiceName() const { return _serviceName; }
+
+ /**
+ * Returns the name of the remote session.
+ *
+ * @return The session name.
+ */
+ const string &getSessionName() const { return _sessionName; }
+
+ /**
+ * Returns the connection spec for the remote service.
+ *
+ * @return The connection spec.
+ */
+ const string &getConnectionSpec() const { return _connectionSpec; }
+
+ /**
+ * Sets the RPC target to be used when communicating with the remove service.
+ *
+ * @param target The target to set.
+ */
+ void setTarget(RPCTarget::SP target) { _target = target; }
+
+ /**
+ * Returns the RPC target to be used when communicating with the remove service. Make sure that {@link
+ * hasTarget()} returns true before calling this method, or you will be deref'ing null.
+ *
+ * @return The target to use.
+ */
+ RPCTarget &getTarget() { return *_target; }
+
+ /**
+ * Returns whether or not this has an RPC target set.
+ *
+ * @return True if target is set.
+ */
+ bool hasTarget() const { return _target.get() != NULL; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpcservicepool.cpp b/messagebus/src/vespa/messagebus/network/rpcservicepool.cpp
new file mode 100644
index 00000000000..9ea0b6fec6a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcservicepool.cpp
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".rpcservicepool");
+
+#include <algorithm>
+#include "rpcservicepool.h"
+#include "rpcnetwork.h"
+
+namespace mbus {
+
+RPCServicePool::RPCServicePool(RPCNetwork &net, uint32_t maxSize) :
+ _net(net),
+ _lru(maxSize)
+{
+ _lru.reserve(maxSize);
+ LOG_ASSERT(maxSize > 0);
+}
+
+RPCServicePool::~RPCServicePool()
+{
+}
+
+RPCServiceAddress::UP
+RPCServicePool::resolve(const string &pattern)
+{
+ if (_lru.hasKey(pattern)) {
+ return _lru[pattern]->resolve();
+ } else {
+ RPCService::LP service(new RPCService(_net.getMirror(), pattern));
+ _lru[pattern] = service;
+ return service->resolve();
+ }
+}
+
+uint32_t
+RPCServicePool::getSize() const
+{
+ return _lru.size();
+}
+
+bool
+RPCServicePool::hasService(const string &pattern) const
+{
+ return _lru.hasKey(pattern);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/rpcservicepool.h b/messagebus/src/vespa/messagebus/network/rpcservicepool.h
new file mode 100644
index 00000000000..e25cfbb4903
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpcservicepool.h
@@ -0,0 +1,66 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include "rpcservice.h"
+#include <vespa/vespalib/stllike/lrucache_map.h>
+
+namespace mbus {
+
+class RPCNetwork;
+
+/**
+ * Class used to reuse services for the same pattern when sending messages over
+ * the rpc network.
+ */
+class RPCServicePool : public boost::noncopyable {
+private:
+ typedef vespalib::lrucache_map< vespalib::LruParam<string, RPCService::LP> > ServiceCache;
+
+ RPCNetwork &_net;
+ ServiceCache _lru;
+
+public:
+ /**
+ * Create a new service pool for the given network.
+ *
+ * @param net The underlying RPC network.
+ * @param maxSize The max number of services to cache.
+ */
+ RPCServicePool(RPCNetwork &net, uint32_t maxSize);
+
+ /**
+ * Destructor. Frees any allocated resources.
+ */
+ ~RPCServicePool();
+
+ /**
+ * Returns the RPCServiceAddress that corresponds to a given pattern. This
+ * reuses the RPCService object for matching pattern so that load balancing
+ * is possible on the network level.
+ *
+ * @param pattern The pattern for the service we require.
+ * @return A service address for the given pattern.
+ */
+ RPCServiceAddress::UP resolve(const string &pattern);
+
+ /**
+ * Returns the number of services available in the pool. This number will
+ * never exceed the limit given at construction time.
+ *
+ * @return The current size of this pool.
+ */
+ uint32_t getSize() const;
+
+ /**
+ * Returns whether or not there is a service available in the pool the
+ * corresponds to the given pattern.
+ *
+ * @param pattern The pattern to check for.
+ * @return True if a corresponding service is in the pool.
+ */
+ bool hasService(const string &pattern) const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpctarget.cpp b/messagebus/src/vespa/messagebus/network/rpctarget.cpp
new file mode 100644
index 00000000000..4fce35c8d16
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpctarget.cpp
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include "rpctarget.h"
+
+namespace mbus {
+
+RPCTarget::RPCTarget(const string &spec, FRT_Supervisor &orb) :
+ _lock(),
+ _orb(orb),
+ _name(spec),
+ _target(*_orb.GetTarget(spec.c_str())),
+ _state(VERSION_NOT_RESOLVED),
+ _version(),
+ _versionHandlers()
+{
+ // empty
+}
+
+RPCTarget::~RPCTarget()
+{
+ _target.SubRef();
+}
+
+void
+RPCTarget::resolveVersion(double timeout, RPCTarget::IVersionHandler &handler)
+{
+ bool hasVersion = false;
+ bool shouldInvoke = false;
+ {
+ vespalib::MonitorGuard guard(_lock);
+ if (_state == VERSION_RESOLVED || _state == PROCESSING_HANDLERS) {
+ while (_state == PROCESSING_HANDLERS) {
+ guard.wait();
+ }
+ hasVersion = true;
+ } else {
+ _versionHandlers.push_back(&handler);
+ if (_state != TARGET_INVOKED) {
+ _state = TARGET_INVOKED;
+ shouldInvoke = true;
+ }
+ }
+ }
+ if (hasVersion) {
+ handler.handleVersion(_version.get());
+ } else if (shouldInvoke) {
+ FRT_RPCRequest *req = _orb.AllocRPCRequest();
+ req->SetMethodName("mbus.getVersion");
+ _target.InvokeAsync(req, timeout, this);
+ }
+}
+
+bool
+RPCTarget::isValid() const
+{
+ vespalib::MonitorGuard guard(_lock);
+ if (_target.IsValid()) {
+ return true;
+ }
+ if (_state == TARGET_INVOKED || _state == PROCESSING_HANDLERS) {
+ return true; // keep alive until RequestDone() is called
+ }
+ return false;
+}
+
+void
+RPCTarget::RequestDone(FRT_RPCRequest *req)
+{
+ HandlerList handlers;
+ {
+ vespalib::MonitorGuard guard(_lock);
+ assert(_state == TARGET_INVOKED);
+ if (req->CheckReturnTypes("s")) {
+ FRT_Values &val = *req->GetReturn();
+ try {
+ _version.reset(new vespalib::Version(val[0]._string._str));
+ } catch (vespalib::IllegalArgumentException &e) {
+ (void)e;
+ }
+ } else if (req->GetErrorCode() == FRTE_RPC_NO_SUCH_METHOD) {
+ _version.reset(new vespalib::Version("4.1"));
+ }
+ _versionHandlers.swap(handlers);
+ _state = PROCESSING_HANDLERS;
+ }
+ for (HandlerList::iterator it = handlers.begin();
+ it != handlers.end(); ++it)
+ {
+ (*it)->handleVersion(_version.get());
+ }
+ {
+ vespalib::MonitorGuard guard(_lock);
+ _state = (_version.get() ? VERSION_RESOLVED : VERSION_NOT_RESOLVED);
+ guard.broadcast();
+ }
+ req->SubRef();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/rpctarget.h b/messagebus/src/vespa/messagebus/network/rpctarget.h
new file mode 100644
index 00000000000..89212c783b0
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpctarget.h
@@ -0,0 +1,120 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/fnet/frt/frt.h>
+#include <vector>
+#include <vespa/vespalib/component/version.h>
+#include <vespa/vespalib/util/sync.h>
+#include <vespa/messagebus/common.h>
+
+namespace mbus {
+
+/**
+ * Implements a target object that encapsulates the FRT connection
+ * target. Instances of this class are returned by {@link RPCService}, and
+ * cached by {@link RPCTargetPool}.
+ */
+class RPCTarget : public FRT_IRequestWait {
+public:
+ /**
+ * Declares a version handler used when resolving the version of a target.
+ * An instance of this is passed to {@link RPCTarget#resolveVersion(double,
+ * VersionHandler)}, and invoked either synchronously or asynchronously,
+ * depending on whether or not the version is already available.
+ */
+ class IVersionHandler {
+ public:
+ /**
+ * Virtual destructor required for inheritance.
+ */
+ virtual ~IVersionHandler() { }
+
+ /**
+ * This method is invoked once the version of the corresponding {@link
+ * RPCTarget} becomes available. If a problem occured while retrieving
+ * the version, this method is invoked with a null argument.
+ *
+ * @param ver The version of corresponding target, or null.
+ */
+ virtual void handleVersion(const vespalib::Version *ver) = 0;
+ };
+
+private:
+ typedef std::vector<IVersionHandler*> HandlerList;
+
+ enum ResolveState {
+ VERSION_NOT_RESOLVED,
+ TARGET_INVOKED,
+ PROCESSING_HANDLERS,
+ VERSION_RESOLVED,
+ };
+ typedef std::unique_ptr<vespalib::Version> Version_UP;
+
+ vespalib::Monitor _lock;
+ FRT_Supervisor &_orb;
+ string _name;
+ FRT_Target &_target;
+ ResolveState _state;
+ Version_UP _version;
+ HandlerList _versionHandlers;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::shared_ptr<RPCTarget> SP;
+
+ /**
+ * Constructs a new instance of this class. This object creates and
+ * takes ownership of a corresponding FRT target, and will deref it
+ * upon destruction.
+ *
+ * @param spec The connection spec of this target.
+ * @param orb The FRT supervisor to use when connecting to target.
+ */
+ RPCTarget(const string &name, FRT_Supervisor &orb);
+
+ /**
+ * Destructor. Subrefs the contained FRT target.
+ */
+ ~RPCTarget();
+
+ /**
+ * Requests the version of this target be passed to the given {@link
+ * VersionHandler}. If the version is available, the handler is called
+ * synchronously; if not, the handler is called by the network thread once
+ * the target responds to the version query.
+ *
+ * @param timeout The timeout for the request in milliseconds.
+ * @param handler The handler to be called once the version is available.
+ */
+ void resolveVersion(double timeout, IVersionHandler &handler);
+
+ /**
+ * @return true if the FRT target is valid or has been invoked (which
+ * means we cannot destroy it).
+ */
+ bool isValid() const;
+
+ /**
+ * Returns the encapsulated FRT target.
+ *
+ * @return The target.
+ */
+ FRT_Target &getFRTTarget() { return _target; }
+
+ /**
+ * Returns the version to use when communicating with this target.
+ * Version must have been successfully resolved before calling this
+ * function.
+ *
+ * @return The negotiated version.
+ */
+ const vespalib::Version &getVersion() const { return *_version; }
+
+ // Implements FRT_IRequestWait.
+ void RequestDone(FRT_RPCRequest *req);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/network/rpctargetpool.cpp b/messagebus/src/vespa/messagebus/network/rpctargetpool.cpp
new file mode 100644
index 00000000000..037e261ad7b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpctargetpool.cpp
@@ -0,0 +1,92 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".rpctargetpool");
+
+#include <vespa/messagebus/systemtimer.h>
+#include "rpctargetpool.h"
+
+namespace mbus {
+
+RPCTargetPool::Entry::Entry(RPCTarget::SP target, uint64_t lastUse) :
+ _target(target),
+ _lastUse(lastUse)
+{
+ // empty
+}
+
+RPCTargetPool::RPCTargetPool(double expireSecs) :
+ _lock(),
+ _targets(),
+ _timer(new SystemTimer()),
+ _expireMillis(static_cast<uint64_t>(expireSecs * 1000))
+{
+ // empty
+}
+
+RPCTargetPool::RPCTargetPool(ITimer::UP timer, double expireSecs) :
+ _lock(),
+ _targets(),
+ _timer(std::move(timer)),
+ _expireMillis(static_cast<uint64_t>(expireSecs * 1000))
+{
+ // empty
+}
+
+RPCTargetPool::~RPCTargetPool()
+{
+ flushTargets(true);
+}
+
+void
+RPCTargetPool::flushTargets(bool force)
+{
+ uint64_t currentTime = _timer->getMilliTime();
+ vespalib::LockGuard guard(_lock);
+ TargetMap::iterator it = _targets.begin();
+ while (it != _targets.end()) {
+ Entry &entry = it->second;
+ if (entry._target.get() != NULL) {
+ if (entry._target.use_count() > 1) {
+ entry._lastUse = currentTime;
+ ++it;
+ continue; // someone is using this
+ }
+ if (!force) {
+ if (--entry._lastUse + _expireMillis > currentTime) {
+ ++it;
+ continue; // not sufficiently idle
+ }
+ }
+ }
+ _targets.erase(it++); // postfix increment to move the iterator
+ }
+}
+
+size_t
+RPCTargetPool::size()
+{
+ vespalib::LockGuard guard(_lock);
+ return _targets.size();
+}
+
+RPCTarget::SP
+RPCTargetPool::getTarget(FRT_Supervisor &orb, const RPCServiceAddress &address)
+{
+ vespalib::LockGuard guard(_lock);
+ string spec = address.getConnectionSpec();
+ TargetMap::iterator it = _targets.find(spec);
+ if (it != _targets.end()) {
+ Entry &entry = it->second;
+ if (entry._target->isValid()) {
+ entry._lastUse = _timer->getMilliTime();
+ return entry._target;
+ }
+ _targets.erase(it);
+ }
+ RPCTarget::SP ret(new RPCTarget(spec, orb));
+ _targets.insert(TargetMap::value_type(spec, Entry(ret, _timer->getMilliTime())));
+ return ret;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/rpctargetpool.h b/messagebus/src/vespa/messagebus/network/rpctargetpool.h
new file mode 100644
index 00000000000..3e4fa953bb5
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/network/rpctargetpool.h
@@ -0,0 +1,97 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <map>
+#include <vespa/messagebus/itimer.h>
+#include <vespa/vespalib/util/sync.h>
+#include "rpcserviceaddress.h"
+#include "rpctarget.h"
+
+class FRT_Supervisor;
+
+namespace mbus {
+
+/**
+ * Class used to reuse targets for the same address when sending messages over
+ * the rpc network.
+ */
+class RPCTargetPool : public boost::noncopyable {
+private:
+ /**
+ * Implements a helper class holds the necessary reference and token counter
+ * for a JRT target to keep connections open as long as they get used from
+ * time to time.
+ */
+ struct Entry {
+ RPCTarget::SP _target;
+ uint64_t _lastUse;
+
+ Entry(RPCTarget::SP target, uint64_t lastUse);
+ };
+ typedef std::map<string, Entry> TargetMap;
+
+ vespalib::Lock _lock;
+ TargetMap _targets;
+ ITimer::UP _timer;
+ uint64_t _expireMillis;
+
+public:
+ /**
+ * Constructs a new instance of this class, and registers the {@link
+ * SystemTimer} for detecting and closing connections that have expired
+ * according to the given parameter.
+ *
+ * @param expireSecs The number of seconds until an idle connection is
+ * closed.
+ */
+ RPCTargetPool(double expireSecs);
+
+ /**
+ * Constructs a new instance of this class, using the given {@link Timer}
+ * for detecting and closing connections that have expired according to the
+ * second paramter.
+ *
+ * @param timer The timer to use for connection expiration.
+ * @param expireSecs The number of seconds until an idle connection is
+ * closed.
+ */
+ RPCTargetPool(ITimer::UP timer, double expireSecs);
+
+ /**
+ * Destructor. Frees any allocated resources.
+ */
+ ~RPCTargetPool();
+
+ /**
+ * This method will return a target for the given address. If a target does
+ * not currently exist for the given address, it will be created and added
+ * to the internal map. Each target is also reference counted so that the
+ * tokens of targets that are currently active is never decremented.
+ *
+ * @param orb The supervisor to use to connect to the target.
+ * @param address The address to resolve to a target.
+ * @return A target for the given address.
+ */
+ RPCTarget::SP getTarget(FRT_Supervisor &orb, const RPCServiceAddress &address);
+
+ /**
+ * Closes all unused target connections. Unless the force argument is true,
+ * this method will allow a grace period for all connections after last use
+ * before it starts closing them. This allows the most recently used
+ * connections to stay open.
+ *
+ * @param force Whether or not to force flush.
+ */
+ void flushTargets(bool force);
+
+ /**
+ * Returns the number of targets currently contained in this.
+ *
+ * @return The size of the internal map.
+ */
+ size_t size();
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/protocolrepository.cpp b/messagebus/src/vespa/messagebus/protocolrepository.cpp
new file mode 100644
index 00000000000..a8435c6a460
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/protocolrepository.cpp
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".protocolrepository");
+
+#include "protocolrepository.h"
+
+namespace mbus {
+
+void
+ProtocolRepository::clearPolicyCache()
+{
+ vespalib::LockGuard guard(_lock);
+ _routingPolicyCache.clear();
+}
+
+IProtocol::SP
+ProtocolRepository::putProtocol(const IProtocol::SP & protocol)
+{
+ vespalib::LockGuard guard(_lock);
+ const string &name = protocol->getName();
+ if (_protocols.find(name) != _protocols.end()) {
+ _routingPolicyCache.clear();
+ }
+ IProtocol::SP prev = _protocols[name];
+ _protocols[name] = protocol;
+ return prev;
+}
+
+bool
+ProtocolRepository::hasProtocol(const string &name) const
+{
+ vespalib::LockGuard guard(_lock);
+ return _protocols.find(name) != _protocols.end();
+}
+
+IProtocol::SP
+ProtocolRepository::getProtocol(const string &name)
+{
+ vespalib::LockGuard guard(_lock);
+ ProtocolMap::iterator it = _protocols.find(name);
+ if (it != _protocols.end()) {
+ return it->second;
+ }
+ return IProtocol::SP();
+}
+
+IRoutingPolicy::SP
+ProtocolRepository::getRoutingPolicy(const string &protocolName,
+ const string &policyName,
+ const string &policyParam)
+{
+ vespalib::LockGuard guard(_lock);
+ string cacheKey = protocolName + "." + policyName + "." + policyParam;
+ RoutingPolicyCache::iterator cit = _routingPolicyCache.find(cacheKey);
+ if (cit != _routingPolicyCache.end()) {
+ return cit->second;
+ }
+ ProtocolMap::iterator pit = _protocols.find(protocolName);
+ if (pit == _protocols.end()) {
+ LOG(error, "Protocol '%s' not supported.", protocolName.c_str());
+ return IRoutingPolicy::SP();
+ }
+ IRoutingPolicy::UP policy;
+ try {
+ policy = pit->second->createPolicy(policyName, policyParam);
+ } catch (const std::exception &e) {
+ LOG(error, "Protocol '%s' threw an exception; %s",
+ protocolName.c_str(), e.what());
+ }
+ if (policy.get() == NULL) {
+ LOG(error, "Protocol '%s' failed to create routing policy '%s' "
+ "with parameter '%s'.",
+ protocolName.c_str(), policyName.c_str(), policyParam.c_str());
+ return IRoutingPolicy::SP();
+ }
+ IRoutingPolicy::SP ret(policy.release());
+ _routingPolicyCache[cacheKey] = ret;
+ return ret;
+}
+
+}
diff --git a/messagebus/src/vespa/messagebus/protocolrepository.h b/messagebus/src/vespa/messagebus/protocolrepository.h
new file mode 100644
index 00000000000..05917a8d5c0
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/protocolrepository.h
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <map>
+#include <vespa/vespalib/util/sync.h>
+#include "iprotocol.h"
+
+namespace mbus {
+
+/**
+ * Implements a thread-safe repository for protocols and their routing policies. This manages an internal cache of
+ * routing policies so that similarly referenced policy directives share the same instance of a policy.
+ */
+class ProtocolRepository : public boost::noncopyable {
+private:
+ typedef std::map<string, IProtocol::SP> ProtocolMap;
+ typedef std::map<string, IRoutingPolicy::SP> RoutingPolicyCache;
+
+ vespalib::Lock _lock;
+ ProtocolMap _protocols;
+ RoutingPolicyCache _routingPolicyCache;
+
+public:
+
+ /**
+ * Registers a protocol with this repository. This will overwrite any protocol that was registered earlier
+ * that has the same name. If this method detects a protocol replacement, it will clear its internal
+ * routing policy cache.
+ *
+ * @param protocol The protocol to register.
+ * @return The previous protocol registered under this name.
+ */
+ IProtocol::SP putProtocol(const IProtocol::SP & protocol);
+
+ /**
+ * Returns whether or not this repository contains a protocol with the given name. Given the concurrent
+ * nature of things, one should not invoke this method followed by {@link #getProtocol(String)} and expect
+ * the return value to be non-null. Instead just get the protocol and compare it to null.
+ *
+ * @param name The name to check for.
+ * @return True if the named protocol is registered.
+ */
+ bool hasProtocol(const string &name) const;
+
+ /**
+ * Returns the protocol whose name matches the given argument. This method will return null if no such
+ * protocol has been registered.
+ *
+ * @param name The name of the protocol to return.
+ * @return The protocol registered, or null.
+ */
+ IProtocol::SP getProtocol(const string &name);
+
+ /**
+ * Creates and returns a routing policy that matches the given arguments. If a routing policy has been
+ * created previously using the exact same parameters, this method will returned that cached instance
+ * instead of creating another. Not that when you replace a protocol using {@link #putProtocol(Protocol)}
+ * the policy cache is cleared.
+ *
+ * @param protocolName The name of the protocol whose routing policy to create.
+ * @param policyName The name of the routing policy to create.
+ * @param policyParam The parameter to pass to the routing policy constructor.
+ * @return The created routing policy.
+ */
+ IRoutingPolicy::SP getRoutingPolicy(const string &protocolName,
+ const string &policyName,
+ const string &policyParam);
+
+ /**
+ * Clears the internal cache of routing policies.
+ */
+ void clearPolicyCache();
+};
+
+}
+
diff --git a/messagebus/src/vespa/messagebus/protocolset.cpp b/messagebus/src/vespa/messagebus/protocolset.cpp
new file mode 100644
index 00000000000..c43e8366bb7
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/protocolset.cpp
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".protocolset");
+#include "protocolset.h"
+
+namespace mbus {
+
+ProtocolSet::ProtocolSet()
+ : _vector()
+{
+}
+
+ProtocolSet &
+ProtocolSet::add(IProtocol::SP protocol)
+{
+ _vector.push_back(protocol);
+ return *this;
+}
+
+bool
+ProtocolSet::empty() const
+{
+ return _vector.empty();
+}
+
+IProtocol::SP
+ProtocolSet::extract()
+{
+ if (_vector.empty()) {
+ return IProtocol::SP();
+ }
+ IProtocol::SP ret = _vector.back();
+ _vector.pop_back();
+ return ret;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/protocolset.h b/messagebus/src/vespa/messagebus/protocolset.h
new file mode 100644
index 00000000000..bb3c29f7397
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/protocolset.h
@@ -0,0 +1,52 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "iprotocol.h"
+
+namespace mbus {
+
+/**
+ * This class is used to bundle a set of IProtocol objects to be
+ * supported by a MessageBus instance.
+ **/
+class ProtocolSet
+{
+private:
+ std::vector<IProtocol::SP> _vector;
+
+public:
+ /**
+ * Create an empty ProtocolSet.
+ **/
+ ProtocolSet();
+
+ /**
+ * Add a Protocol to this set.
+ *
+ * @return this object, to allow chaining
+ * @param protocol the IProtocol we want to add
+ **/
+ ProtocolSet &add(IProtocol::SP protocol);
+
+ /**
+ * Check if this set is empty
+ *
+ * @return true if this set is empty
+ **/
+ bool empty() const;
+
+ /**
+ * Extract a single protocol from this set. This will remove the
+ * protocol from the set. If the set is empty, a shared pointer to
+ * 0 will be returned.
+ *
+ * @return the extracted IProtocol
+ **/
+ IProtocol::SP extract();
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/queue.h b/messagebus/src/vespa/messagebus/queue.h
new file mode 100644
index 00000000000..e65dcd2801a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/queue.h
@@ -0,0 +1,64 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <queue>
+
+namespace mbus {
+
+/**
+ * A simple generic queue implementation that is not thread-safe. The
+ * API is similar to that of std::queue.
+ **/
+template <class T>
+class Queue
+{
+private:
+ std::queue<T> _queue;
+
+public:
+ /**
+ * Create an empty Queue.
+ **/
+ Queue() : _queue() {}
+
+ /**
+ * Obtain the size of this queue. The size denotes the number of
+ * elements currently on the queue.
+ *
+ * @return size current queue size
+ **/
+ uint32_t size() const {
+ return _queue.size();
+ }
+
+ /**
+ * Access the element located at the front of the queue. This
+ * method yields undefined behavior if the queue is empty.
+ *
+ * @return element at the front of this queue
+ **/
+ T &front() {
+ return _queue.front();
+ }
+
+ /**
+ * Push an element to the back of this queue.
+ *
+ * @param val the element value
+ **/
+ void push(const T &val) {
+ _queue.push(val);
+ }
+
+ /**
+ * Pop the front element from this queue. This method yields
+ * undefined behavior if the queue is empty.
+ **/
+ void pop() {
+ _queue.pop();
+ }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/reply.cpp b/messagebus/src/vespa/messagebus/reply.cpp
new file mode 100644
index 00000000000..b911ab5f8e9
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/reply.cpp
@@ -0,0 +1,87 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".reply");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <vespa/vespalib/util/backtrace.h>
+#include "emptyreply.h"
+#include "error.h"
+#include "errorcode.h"
+#include "ireplyhandler.h"
+#include "message.h"
+#include "reply.h"
+#include "tracelevel.h"
+
+namespace mbus {
+
+Reply::Reply() :
+ _errors(),
+ _msg(),
+ _retryDelay(-1.0)
+{
+ // empty
+}
+
+Reply::~Reply()
+{
+ if (getCallStack().size() > 0) {
+ string backtrace = vespalib::getStackTrace(0);
+ LOG(warning, "Deleted reply %p with non-empty call-stack. Deleted at:\n%s",
+ this, backtrace.c_str());
+ Reply::UP reply(new EmptyReply());
+ swapState(*reply);
+ reply->addError(Error(ErrorCode::FATAL_ERROR,
+ "The reply object was deleted while containing state information; "
+ "generating an auto-reply."));
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+ }
+}
+
+void
+Reply::swapState(Routable &rhs)
+{
+ Routable::swapState(rhs);
+ if (rhs.isReply()) {
+ Reply &reply = static_cast<Reply&>(rhs);
+
+ std::swap(_retryDelay, reply._retryDelay);
+
+ Message::UP msg = std::move(_msg);
+ _msg = std::move(reply._msg);
+ reply._msg = std::move(msg);
+
+ reply._errors.swap(_errors);
+ }
+}
+
+bool
+Reply::isReply() const
+{
+ return true;
+}
+
+void
+Reply::addError(const Error &e)
+{
+ if (getTrace().shouldTrace(TraceLevel::ERROR)) {
+ getTrace().trace(TraceLevel::ERROR, e.toString());
+ }
+ _errors.push_back(e);
+}
+
+bool
+Reply::hasFatalErrors() const
+{
+ for (std::vector<Error>::const_iterator it = _errors.begin();
+ it != _errors.end(); ++it)
+ {
+ if (it->getCode() >= ErrorCode::FATAL_ERROR) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/reply.h b/messagebus/src/vespa/messagebus/reply.h
new file mode 100644
index 00000000000..41c9bb2c3e0
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/reply.h
@@ -0,0 +1,129 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vector>
+#include "error.h"
+#include "message.h"
+
+namespace mbus {
+
+class Message;
+
+/**
+ * A reply is a response to a message that has been sent throught the
+ * messagebus. No reply will ever exist without a corresponding message. There
+ * are no error-replies defined, as errors can instead piggyback any reply by
+ * the {@link #errors} member variable.
+ */
+class Reply : public Routable {
+private:
+ std::vector<Error> _errors; // A list of errors that have occured during the lifetime of this reply.
+ Message::UP _msg; // The message to which this is a reply.
+ double _retryDelay; // How to perform resending of this.
+
+public:
+ /**
+ * Convenience typedef for an auto pointer to a Reply object.
+ */
+ typedef std::unique_ptr<Reply> UP;
+
+ /**
+ * Constructs a new instance of this class. This object is useless until the
+ * state of a real message is swapped with this using {@link
+ * #swapState(Routable)}.
+ */
+ Reply();
+
+ /**
+ * If a reply is deleted with elements on the callstack, this destructor
+ * will log an error and generate an auto-reply to avoid having the sender
+ * wait indefinetly for a reply.
+ */
+ virtual ~Reply();
+
+ // Inherit doc from Routable.
+ virtual void swapState(Routable &rhs);
+
+ /**
+ * Inherited from Routable. Classifies this object as 'a reply'.
+ *
+ * @return true
+ */
+ virtual bool isReply() const;
+
+ /**
+ * Add an Error to this Reply
+ *
+ * @param error the error to add
+ */
+ void addError(const Error &error);
+
+ /**
+ * Returns whether or not this reply contains at least one error.
+ *
+ * @return True if this contains errors.
+ */
+ bool hasErrors() const { return ! _errors.empty(); }
+
+ /**
+ * Returns whether or not this reply contains any fatal errors.
+ *
+ * @return True if it contains fatal errors.
+ */
+ bool hasFatalErrors() const;
+
+ /**
+ * Returns the error at the given position.
+ *
+ * @param i The index of the error to return.
+ * @return The error at the given index.
+ */
+ const Error &getError(uint32_t i) const { return _errors[i]; }
+
+ /**
+ * Returns the number of errors that this reply contains.
+ *
+ * @return The number of replies.
+ */
+ uint32_t getNumErrors() const { return _errors.size(); }
+
+ /**
+ * Attach a Message to this Reply. If a Reply contains errors, messagebus
+ * will attach the original Message to the Reply before giving it to the
+ * application.
+ *
+ * @param msg the Message to attach
+ */
+ void setMessage(Message::UP msg) { _msg = std::move(msg); }
+
+ /**
+ * Detach the Message attached to this Reply. If a Reply contains errors,
+ * messagebus will attach the original Message to the Reply before giving it
+ * to the application.
+ *
+ * @return the detached Message
+ */
+ Message::UP getMessage() { return std::move(_msg); }
+
+ /**
+ * Returns the retry request of this reply. This can be set using {@link
+ * #setRetryDelay} and is an instruction to the resender logic of message
+ * bus on how to perform the retry. If this value is anything other than a
+ * negative number, it instructs the resender to disregard all configured
+ * resending attributes and instead act according to this value.
+ *
+ * @return The retry request.
+ */
+ double getRetryDelay() const { return _retryDelay; }
+
+ /**
+ * Sets the retry delay request of this reply. If this is a negative number,
+ * it will use the defaults configured in the source session.
+ *
+ * @param retryDelay The retry request.
+ */
+ void setRetryDelay(double retryDelay) { _retryDelay = retryDelay; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/replygate.cpp b/messagebus/src/vespa/messagebus/replygate.cpp
new file mode 100644
index 00000000000..8363d17fd89
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/replygate.cpp
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".replygate");
+
+#include "replygate.h"
+
+namespace mbus {
+
+ReplyGate::ReplyGate(IMessageHandler &sender) :
+ vespalib::ReferenceCounter(),
+ _sender(sender),
+ _open(true)
+{
+ // empty
+}
+
+void
+ReplyGate::handleMessage(Message::UP msg)
+{
+ addRef();
+ msg->pushHandler(*this, *this);
+ _sender.handleMessage(std::move(msg));
+}
+
+void
+ReplyGate::close()
+{
+ _open = false;
+}
+
+void
+ReplyGate::handleReply(Reply::UP reply)
+{
+ if (_open) {
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+ } else {
+ reply->discard();
+ }
+ subRef();
+}
+
+void
+ReplyGate::handleDiscard(Context ctx)
+{
+ (void)ctx;
+ subRef();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/replygate.h b/messagebus/src/vespa/messagebus/replygate.h
new file mode 100644
index 00000000000..121a66edd3f
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/replygate.h
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/vespalib/util/referencecounter.h>
+#include "idiscardhandler.h"
+#include "imessagehandler.h"
+#include "ireplyhandler.h"
+#include "message.h"
+
+namespace mbus {
+
+/**
+ * A ReplyGate will forward replies until it is closed. After being closed, the
+ * gate will silently delete all replies. The ReplyGate class has external
+ * reference counting. This class is used by session objects to perform safe
+ * untangling from messagebus when being destructed while having pending
+ * messages. The reference counting is needed to ensure that the object is alive
+ * until all pending replies have been correctly ignored. Thread synchronization
+ * is handled outside this class. Note that this class is only intended for
+ * internal use.
+ */
+class ReplyGate : public boost::noncopyable,
+ public vespalib::ReferenceCounter,
+ public IDiscardHandler,
+ public IMessageHandler,
+ public IReplyHandler
+{
+private:
+ IMessageHandler &_sender;
+ bool _open;
+
+public:
+ /**
+ * Create a new ReplyGate.
+ *
+ * @param sender The underlying IMessageHandler object.
+ */
+ ReplyGate(IMessageHandler &sender);
+
+ /**
+ * Send a Message to the underlying IMessageHandler. This method will
+ * increase the reference counter to ensure that this object is alive until
+ * the matching Reply has been obtained. In order to obtain the matching
+ * Reply, this method will push this object on the CallStack of the Message.
+ */
+ void handleMessage(Message::UP msg);
+
+ /**
+ * Forward or discard Reply. If the gate is still open, it will forward the
+ * Reply to the next IReplyHandler on the CallStack. If the gate is closed,
+ * the Reply will be discarded. This method also decreases the reference
+ * counter of this object.
+ */
+ void handleReply(Reply::UP reply);
+
+ // Implements IDiscardHandler.
+ void handleDiscard(Context ctx);
+
+ /**
+ * Close this gate. After this has been invoked, the gate will start to
+ * discard Reply objects. A closed gate can never be re-opened.
+ */
+ void close();
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/result.cpp b/messagebus/src/vespa/messagebus/result.cpp
new file mode 100644
index 00000000000..81a55da1276
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/result.cpp
@@ -0,0 +1,86 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".result");
+#include "result.h"
+
+namespace mbus {
+
+Result::Handover::Handover(bool a, const Error &e, Message *m)
+ : _accepted(a),
+ _error(e),
+ _msg(m)
+{
+}
+
+Result::Result()
+ : _accepted(true),
+ _error(),
+ _msg()
+{
+}
+
+Result::Result(const Error &err, Message::UP msg)
+ : _accepted(false),
+ _error(err),
+ _msg(std::move(msg))
+{
+}
+
+Result::Result(Result &&rhs)
+ : _accepted(rhs._accepted),
+ _error(rhs._error),
+ _msg(std::move(rhs._msg))
+{
+}
+
+Result::Result(const Handover &rhs)
+ : _accepted(rhs._accepted),
+ _error(rhs._error),
+ _msg(rhs._msg)
+{
+}
+
+bool
+Result::isAccepted() const
+{
+ return _accepted;
+}
+
+const Error &
+Result::getError() const
+{
+ return _error;
+}
+
+Message::UP
+Result::getMessage()
+{
+ return std::move(_msg);
+}
+
+Result::operator Handover()
+{
+ return Handover(_accepted, _error, _msg.release());
+}
+
+Result &
+Result::operator=(Result &&rhs)
+{
+ _accepted = rhs._accepted;
+ _error = rhs._error;
+ _msg = std::move(rhs._msg);
+ return *this;
+}
+
+Result &
+Result::operator=(const Handover &rhs)
+{
+ _accepted = rhs._accepted;
+ _error = rhs._error;
+ _msg.reset(rhs._msg);
+ return *this;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/result.h b/messagebus/src/vespa/messagebus/result.h
new file mode 100644
index 00000000000..02f4ba1e838
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/result.h
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include "error.h"
+#include "message.h"
+
+namespace mbus {
+
+/**
+ * A Result object is used as return value when trying to send a
+ * Message on a SourceSession. It says whether messagebus has accepted
+ * the message or not. If messagebus accepts the message an
+ * asynchronous reply will be delivered at a later time. If messagebus
+ * does not accept the message, the returned Result will indicate why
+ * the message was not accepted. A Result indication an error will
+ * also contain the message that did not get accepted, passing it back
+ * to the application. Note that Result objects have destructive copy
+ * of the pointer to the Message that is handed back to the
+ * application.
+ **/
+class Result
+{
+private:
+ bool _accepted;
+ Error _error;
+ Message::UP _msg;
+
+public:
+ /**
+ * This inner class is used to implement destructive copy for
+ * return values.
+ **/
+ class Handover {
+ friend class Result;
+ bool _accepted;
+ Error _error;
+ Message *_msg;
+ Handover(bool a, const Error &e, Message *m);
+ Handover(const Handover &); // not implemented
+ Handover &operator=(const Handover &); // not implemented
+ };
+
+ /**
+ * Create a Result indicating that messagebus has accepted the
+ * Message.
+ **/
+ Result();
+
+ /**
+ * Create a Result indicating that messagebus has not accepted the
+ * Message.
+ *
+ * @param err the reason for not accepting the Message
+ * @param msg the message that did not get accepted
+ **/
+ Result(const Error &err, Message::UP msg);
+
+ /**
+ * Move constructor
+ *
+ * @param rhs the original object
+ **/
+ Result(Result &&rhs);
+
+ /**
+ * Construct a new Result from an internal Handover object that
+ * has destructed the original Result.
+ *
+ * @param rhs handover object
+ **/
+ Result(const Handover &rhs);
+
+ /**
+ * Check if the message was accepted.
+ *
+ * @return true if the Message was accepted
+ **/
+ bool isAccepted() const;
+
+ /**
+ * Obtain the error causing the message not to be accepted.
+ *
+ * @return error
+ **/
+ const Error &getError() const;
+
+ /**
+ * If the message was not accepted, this method may be used to get
+ * the Message back out. Note that this method hands the Message
+ * over to the caller. Also note that copying the Result will
+ * transfer the ownership of the Message to the new copy.
+ *
+ * @return the Message that was not accepted
+ **/
+ Message::UP getMessage();
+
+ /**
+ * Perform an implicit typecast to support destructive copy of
+ * return values.
+ **/
+ operator Handover();
+
+ /**
+ * Moving assignment operator
+ *
+ * @param rhs the original object
+ **/
+ Result &operator=(Result &&rhs);
+
+ /**
+ * Assign a Result from an internal Handover object that has
+ * destructed the original Result.
+ *
+ * @param rhs handover object
+ **/
+ Result &operator=(const Handover &rhs);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routable.cpp b/messagebus/src/vespa/messagebus/routable.cpp
new file mode 100644
index 00000000000..95d56e09696
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routable.cpp
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routable");
+#include "emptyreply.h"
+#include "errorcode.h"
+#include "ireplyhandler.h"
+
+namespace mbus {
+
+Routable::Routable() :
+ _context(),
+ _stack(),
+ _trace()
+{
+ // empty
+}
+
+void
+Routable::discard()
+{
+ _context = Context();
+ _stack.discard();
+ _trace.clear();
+}
+
+void
+Routable::swapState(Routable &rhs)
+{
+ std::swap(_context, rhs._context);
+ _stack.swap(rhs._stack);
+ _trace.swap(rhs._trace);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routable.h b/messagebus/src/vespa/messagebus/routable.h
new file mode 100644
index 00000000000..d40c80a736b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routable.h
@@ -0,0 +1,175 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <boost/utility.hpp>
+#include <vespa/messagebus/blob.h>
+#include <vespa/messagebus/callstack.h>
+#include <vespa/messagebus/context.h>
+#include <vespa/messagebus/trace.h>
+
+namespace mbus {
+
+/**
+ * Superclass for objects that can be either explicitly (Message) or implicitly
+ * (Reply) routed. Note that protocol implementors should never subclass this
+ * directly, but rather through the {@link Message} and {@link Reply} classes.
+ *
+ * A routable can be regarded as a protocol-defined value with additional
+ * message bus related state. The state is what differentiates two Routables
+ * that carry the same value. This includes the application context attached to
+ * the routable and the {@link CallStack} used to track the path of the routable
+ * within messagebus. When a routable is copied (if the protocol supports it)
+ * only the value part is copied. The state must be explicitly transfered by
+ * invoking the {@link #swapState(Routable)} method. That method is used to
+ * transfer the state from a message to the corresponding reply, or to a
+ * different message if the application decides to replace it.
+ */
+class Routable : public boost::noncopyable {
+private:
+ Context _context;
+ CallStack _stack;
+ Trace _trace;
+
+public:
+ /**
+ * Convenience typedef for an auto pointer to a Routable object.
+ */
+ typedef std::unique_ptr<Routable> UP;
+
+ /**
+ * Constructs a new instance of this class.
+ */
+ Routable();
+
+ /**
+ * Required for inheritance.
+ */
+ virtual ~Routable() { /* empty */ }
+
+ /**
+ * Discards this routable. Invoking this prevents the auto-generation of
+ * replies if you later discard the routable. This is a required step to
+ * ensure safe shutdown if you need destroy a message bus instance while
+ * there are still messages and replies alive in your application.
+ */
+ void discard();
+
+ /**
+ * Access the CallStack of this routable. The CallStack is intended for
+ * internal messagebus use.
+ *
+ * @return reference to internal CallStack
+ */
+ CallStack &getCallStack() { return _stack; }
+
+ /**
+ * Pushes the given reply handler onto the call stack of this routable, also
+ * storing the current context.
+ *
+ * @param handler The handler to push.
+ */
+ void pushHandler(IReplyHandler &handler) { _stack.push(handler, _context); }
+
+ /**
+ * Pushes the given reply- and discard handler onto the call stack of this
+ * routable, also storing the current context.
+ *
+ * @param replyHandler The handler called if the reply arrives.
+ * @param discardHandler The handler called if the reply is dicarded.
+ */
+ void pushHandler(IReplyHandler &replyHandler, IDiscardHandler &discardHandler) {
+ _stack.push(replyHandler, _context, &discardHandler);
+ }
+
+ /**
+ * Access the Trace object for this Routable. The Trace is part of the
+ * object state and will not be copied along with the value part of a
+ * Routable. The swapState method will transfer the Trace from one Routable
+ * to another.
+ *
+ * @return Trace object
+ */
+ Trace &getTrace() { return _trace; }
+
+ /**
+ * Access the Trace object for this Routable. The Trace is part of the
+ * object state and will not be copied along with the value part of a
+ * Routable. The swapState method will transfer the Trace from one Routable
+ * to another.
+ *
+ * @return Trace object
+ */
+ const Trace &getTrace() const { return _trace; }
+
+ /**
+ * Sets the Trace object for this Routable.
+ *
+ * @param trace The trace to set.
+ */
+ void setTrace(const Trace &trace) { _trace = trace; }
+
+ /**
+ * Swaps the state that makes this routable unique to another routable. The
+ * state is what identifies a routable for message bus, so only one message
+ * can ever have the same state. This function must be called explicitly
+ * when cloning and copying messages.
+ *
+ * @param rhs The routable to swap state with.
+ */
+ virtual void swapState(Routable &rhs);
+
+ /**
+ * Get the context of this routable.
+ *
+ * @return the context
+ */
+ Context getContext() const { return _context; }
+
+ /**
+ * Set the context of this routable. When setting a context on a Message,
+ * the Reply for that Message will have the same context as the original
+ * Message when received.
+ *
+ * @param ctx the context
+ */
+ void setContext(Context ctx) { _context = ctx; }
+
+ /**
+ * Check whether this routable is a reply.
+ *
+ * @return true if this is a reply
+ */
+ virtual bool isReply() const = 0;
+
+ /**
+ * Obtain the name of the protocol for this routable. This method must be
+ * implemented by all routable classes part of a protocol.
+ */
+ virtual const string &getProtocol() const = 0;
+
+ /**
+ * Return the type of this routable. The type is protocol specific with the
+ * exception that the value 0 is reserved for the EmptyReply class. This
+ * value is typically intended to be used by the application to identify
+ * what kind of routable object it is looking at.
+ *
+ * @return routable type id
+ */
+ virtual uint32_t getType() const = 0;
+
+ /**
+ * Returns the priority of this message. 0 is most highly
+ * prioritized, and messages with higher priority will be sent
+ * earlier than other messages.
+ */
+ virtual uint8_t priority() const = 0;
+
+ /**
+ * Returns a string representation of this message.
+ */
+ virtual string toString() const { return ""; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routablequeue.cpp b/messagebus/src/vespa/messagebus/routablequeue.cpp
new file mode 100644
index 00000000000..8e255f8b393
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routablequeue.cpp
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routablequeue");
+#include "routablequeue.h"
+
+namespace mbus {
+
+RoutableQueue::RoutableQueue()
+ : _monitor("mbus::RoutableQueue::_monitor", true),
+ _queue()
+{
+}
+
+RoutableQueue::~RoutableQueue()
+{
+ while (_queue.size() > 0) {
+ Routable *r = _queue.front();
+ _queue.pop();
+ delete r;
+ }
+}
+
+uint32_t
+RoutableQueue::size()
+{
+ vespalib::MonitorGuard guard(_monitor);
+ return _queue.size();
+}
+
+void
+RoutableQueue::enqueue(Routable::UP r)
+{
+ vespalib::MonitorGuard guard(_monitor);
+ _queue.push(r.get());
+ r.release();
+ if (_queue.size() == 1) {
+ guard.broadcast(); // support multiple readers
+ }
+}
+
+Routable::UP
+RoutableQueue::dequeue(uint32_t msTimeout)
+{
+ FastOS_Time t;
+ t.SetNow();
+ uint32_t msLeft = msTimeout;
+ vespalib::MonitorGuard guard(_monitor);
+ while (_queue.size() == 0 && msLeft > 0) {
+ if (!guard.wait(msLeft) || _queue.size() > 0) {
+ break;
+ }
+ uint32_t elapsed = (uint32_t)t.MilliSecsToNow();
+ msLeft = (elapsed > msTimeout) ? 0 : msTimeout - elapsed;
+ }
+ if (_queue.size() == 0) {
+ return Routable::UP();
+ }
+ Routable::UP ret(_queue.front());
+ _queue.pop();
+ return ret;
+}
+
+void
+RoutableQueue::handleMessage(Message::UP msg)
+{
+ Routable::UP r(msg.release());
+ enqueue(std::move(r));
+}
+
+void
+RoutableQueue::handleReply(Reply::UP reply)
+{
+ Routable::UP r(reply.release());
+ enqueue(std::move(r));
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routablequeue.h b/messagebus/src/vespa/messagebus/routablequeue.h
new file mode 100644
index 00000000000..f3174abf5f5
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routablequeue.h
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/sync.h>
+#include "imessagehandler.h"
+#include "ireplyhandler.h"
+#include "queue.h"
+#include "routable.h"
+#include "message.h"
+#include "reply.h"
+
+namespace mbus {
+
+/**
+ * A RoutableQueue is a queue of Routable objects that also implements
+ * the IMessageHandler and IReplyHandler APIs. This class is included
+ * as a convenience for application developers that does not want to
+ * write their own Message and Reply handlers for use with session
+ * objects. Note that a RoutableQueue cannot be copied and that it
+ * owns all objects currently on the queue. The RoutableQueue class is
+ * thread-safe.
+ **/
+class RoutableQueue : public IMessageHandler,
+ public IReplyHandler
+{
+private:
+ vespalib::Monitor _monitor;
+ Queue<Routable*> _queue;
+
+ RoutableQueue(const RoutableQueue &);
+ RoutableQueue &operator=(const RoutableQueue &);
+
+public:
+ /**
+ * Create an empty queue.
+ **/
+ RoutableQueue();
+
+ /**
+ * The destructor will delete any objects still on the queue.
+ **/
+ virtual ~RoutableQueue();
+
+ /**
+ * Obtain the number of elements currently in this queue. Note
+ * that the return value of this method may become invalid really
+ * fast if the queue is used by multiple threads.
+ *
+ * @return current queue size
+ **/
+ uint32_t size();
+
+ /**
+ * Enqueue a routable on this queue.
+ *
+ * @param r the Routable to enqueue
+ **/
+ void enqueue(Routable::UP r);
+
+ /**
+ * Dequeue a routable from this queue. This method will block if
+ * the queue is currently empty. The 'msTimeout' parameter
+ * indicate how long to wait for something to be enqueued. If 0 is
+ * given as timeout, this method will not block at all, making it
+ * perform a simple poll. If the dequeue operation times out, the
+ * returned auto-pointer will point to 0.
+ *
+ * @return the dequeued routable
+ * @param msTimeout how long to wait if the queue is empty
+ **/
+ Routable::UP dequeue(uint32_t msTimeout);
+
+ /**
+ * Handle a Message by enqueuing it.
+ *
+ * @param msg the Message to handle
+ **/
+ virtual void handleMessage(Message::UP msg);
+
+ /**
+ * Handle a Reply by enqueuing it.
+ *
+ * @param reply the Reply to handle
+ **/
+ virtual void handleReply(Reply::UP reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/.gitignore b/messagebus/src/vespa/messagebus/routing/.gitignore
new file mode 100644
index 00000000000..1465a0826cb
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/.gitignore
@@ -0,0 +1,5 @@
+.depend
+Makefile
+lex.yy.cpp
+parser.tab.cpp
+parser.tab.h
diff --git a/messagebus/src/vespa/messagebus/routing/CMakeLists.txt b/messagebus/src/vespa/messagebus/routing/CMakeLists.txt
new file mode 100644
index 00000000000..2edb990809e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(messagebus_routing OBJECT
+ SOURCES
+ errordirective.cpp
+ hop.cpp
+ hopblueprint.cpp
+ hopspec.cpp
+ policydirective.cpp
+ resender.cpp
+ retrytransienterrorspolicy.cpp
+ route.cpp
+ routedirective.cpp
+ routeparser.cpp
+ routespec.cpp
+ routingcontext.cpp
+ routingnode.cpp
+ routingnodeiterator.cpp
+ routingspec.cpp
+ routingtable.cpp
+ routingtablespec.cpp
+ tcpdirective.cpp
+ verbatimdirective.cpp
+ DEPENDS
+)
diff --git a/messagebus/src/vespa/messagebus/routing/errordirective.cpp b/messagebus/src/vespa/messagebus/routing/errordirective.cpp
new file mode 100644
index 00000000000..f87698da1ba
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/errordirective.cpp
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "errordirective.h"
+
+namespace mbus {
+
+ErrorDirective::ErrorDirective(const vespalib::stringref &msg) :
+ _msg(msg)
+{
+ // empty
+}
+
+string
+ErrorDirective::toString() const
+{
+ return vespalib::make_vespa_string("(%s)", _msg.c_str());
+}
+
+string
+ErrorDirective::toDebugString() const
+{
+ return vespalib::make_vespa_string("ErrorDirective(msg = '%s')", _msg.c_str());
+}
+
+} // mbus
diff --git a/messagebus/src/vespa/messagebus/routing/errordirective.h b/messagebus/src/vespa/messagebus/routing/errordirective.h
new file mode 100644
index 00000000000..d55596e2871
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/errordirective.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "ihopdirective.h"
+
+namespace mbus {
+
+/**
+ * This class represents an error directive within a {@link Hop}'s selector. This means to stop whatever is being
+ * resolved, and instead return a reply containing a specified error.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class ErrorDirective : public IHopDirective {
+private:
+ string _msg;
+
+public:
+ /**
+ * Constructs a new error directive.
+ *
+ * @param msg The error message.
+ */
+ ErrorDirective(const vespalib::stringref &msg);
+
+ /**
+ * Returns the error string that is to be assigned to the reply.
+ *
+ * @return The error string.
+ */
+ const string &getMessage() const { return _msg; }
+
+ virtual Type getType() const { return TYPE_ERROR; }
+ virtual bool matches(const IHopDirective &) const { return false; }
+ virtual string toString() const;
+ virtual string toDebugString() const;
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/hop.cpp b/messagebus/src/vespa/messagebus/routing/hop.cpp
new file mode 100644
index 00000000000..5a38eba907c
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/hop.cpp
@@ -0,0 +1,148 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".hop");
+
+#include "hop.h"
+#include "routeparser.h"
+
+namespace mbus {
+
+Hop::Hop() :
+ _selector(),
+ _ignoreResult(false)
+{
+ // empty
+}
+
+Hop::Hop(const string &selector) :
+ _selector(),
+ _ignoreResult(false)
+{
+ Hop hop = parse(selector);
+ _selector.swap(hop._selector);
+ _ignoreResult = hop._ignoreResult;
+}
+
+Hop::Hop(const std::vector<IHopDirective::SP> &selector,
+ bool ignoreResult) :
+ _selector(selector),
+ _ignoreResult(ignoreResult)
+{
+ // empty
+}
+
+Hop &
+Hop::addDirective(IHopDirective::SP dir)
+{
+ _selector.push_back(dir);
+ return *this;
+}
+
+Hop &
+Hop::setDirective(uint32_t i, IHopDirective::SP dir)
+{
+ _selector[i] = dir;
+ return *this;
+}
+
+IHopDirective::SP
+Hop::removeDirective(uint32_t i)
+{
+ IHopDirective::SP ret = _selector[i];
+ _selector.erase(_selector.begin() + i);
+ return ret;
+}
+
+Hop &
+Hop::clearDirectives()
+{
+ _selector.clear();
+ return *this;
+}
+
+Hop &
+Hop::setIgnoreResult(bool ignoreResult)
+{
+ _ignoreResult = ignoreResult;
+ return *this;
+}
+
+Hop
+Hop::parse(const string &hop)
+{
+ return RouteParser::createHop(hop);
+}
+
+bool
+Hop::matches(const Hop &hop) const
+{
+ if (_selector.size() != hop.getNumDirectives()) {
+ return false;
+ }
+ for (uint32_t i = 0; i < hop.getNumDirectives(); ++i) {
+ if (!_selector[i]->matches(*hop.getDirective(i))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+string
+Hop::toDebugString() const
+{
+ string ret = "Hop(selector = { ";
+ for (uint32_t i = 0; i < _selector.size(); ++i) {
+ ret.append(_selector[i]->toDebugString());
+ if (i < _selector.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" }, ignoreResult = ");
+ ret.append(_ignoreResult ? "true" : "false");
+ ret.append(")");
+ return ret;
+}
+
+string
+Hop::toString() const
+{
+ string ret = _ignoreResult ? "?" : "";
+ ret.append(toString(0, _selector.size()));
+ return ret;
+}
+
+string
+Hop::toString(uint32_t fromIncluding, uint32_t toNotIncluding) const
+{
+ string ret = "";
+ for (uint32_t i = fromIncluding; i < toNotIncluding; ++i) {
+ ret.append(_selector[i]->toString());
+ if (i < toNotIncluding - 1) {
+ ret.append("/");
+ }
+ }
+ return ret;
+}
+
+string
+Hop::getPrefix(uint32_t toNotIncluding) const
+{
+ if (toNotIncluding > 0) {
+ return toString(0, toNotIncluding) + "/";
+ }
+ return "";
+}
+
+string
+Hop::getSuffix(uint32_t fromNotIncluding) const
+{
+ if (fromNotIncluding < _selector.size() - 1) {
+ string ret = "/";
+ ret.append(toString(fromNotIncluding + 1, _selector.size()));
+ return ret;
+ }
+ return "";
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/hop.h b/messagebus/src/vespa/messagebus/routing/hop.h
new file mode 100644
index 00000000000..6d23cda729c
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/hop.h
@@ -0,0 +1,180 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <stdint.h>
+#include <vector>
+#include "ihopdirective.h"
+
+namespace mbus {
+
+/**
+ * A hop is basically an instantiated {@link HopBlueprint}, but it can also be contructed using the factory method
+ * {@link this#parse(String)}. It is a set of primitives, either a string primitive that is to be matched verbatim to a
+ * service address, or a {@link RoutingPolicy} directive.
+ */
+class Hop {
+private:
+ std::vector<IHopDirective::SP> _selector;
+ bool _ignoreResult;
+
+public:
+ /**
+ * Convenience typedef for an auto-pointer to a hop.
+ */
+ typedef std::unique_ptr<Hop> UP;
+
+ /**
+ * Constructs an empty hop.
+ */
+ Hop();
+
+ /**
+ * Constructs a new hop based on a selector string.
+ *
+ * @param selector The selector string for this hop.
+ */
+ Hop(const string &selector);
+
+ /**
+ * Constructs a fully populated hop.
+ *
+ * @param selector The selector to copy.
+ * @param ignoreResult Whether or not to ignore the result of this hop.
+ */
+ Hop(const std::vector<IHopDirective::SP> &selector, bool ignoreResult);
+
+ /**
+ * Adds a new directive to this hop.
+ *
+ * @param directive The directive to add.
+ * @return This, to allow chaining.
+ */
+ Hop &addDirective(IHopDirective::SP dir);
+
+ /**
+ * Returns whether or not there are any directives contained in this hop.
+ *
+ * @return True if there is at least one directive.
+ */
+ bool hasDirectives() const { return !_selector.empty(); }
+
+ /**
+ * Returns the number of directives contained in this hop.
+ *
+ * @return The number of directives.
+ */
+ uint32_t getNumDirectives() const { return _selector.size(); }
+
+ /**
+ * Returns the directive at the given index.
+ *
+ * @param i The index of the directive to return.
+ * @return The item.
+ */
+ IHopDirective::SP getDirective(uint32_t i) const { return _selector[i]; }
+
+ /**
+ * Sets the directive at a given index.
+ *
+ * @param i The index at which to set the directive.
+ * @param dir The directive to set.
+ * @return This, to allow chaining.
+ */
+ Hop &setDirective(uint32_t i, IHopDirective::SP dir);
+
+ /**
+ * Removes the directive at the given index.
+ *
+ * @param i The index of the directive to remove.
+ * @return The removed directive.
+ */
+ IHopDirective::SP removeDirective(uint32_t i);
+
+ /**
+ * Clears all directives from this hop.
+ *
+ * @return This, to allow chaining.
+ */
+ Hop &clearDirectives();
+
+ /**
+ * Returns the service name referenced by this hop. This is the concatenation of all selector primitives,
+ * but with no ignore-result prefix.
+ *
+ * @return The service name.
+ */
+ string getServiceName() const { return toString(0, _selector.size()); }
+
+ /**
+ * Returns whether or not to ignore the result when routing through this hop.
+ *
+ * @return True to ignore the result.
+ */
+ bool getIgnoreResult() const { return _ignoreResult; }
+
+ /**
+ * Sets whether or not to ignore the result when routing through this hop.
+ *
+ * @param ignoreResult Whether or not to ignore the result.
+ * @return This, to allow chaining.
+ */
+ Hop &setIgnoreResult(bool ignoreResult);
+
+ /**
+ * Parses the given string as a single hop. The {@link this#toString()} method is compatible with this parser.
+ *
+ * @param hop The string to parse.
+ * @return A hop that corresponds to the string.
+ */
+ static Hop parse(const string &hop);
+
+ /**
+ * Returns true whether this hop matches another. This respects policy directives matching any other.
+ *
+ * @param hop The hop to compare to.
+ * @return True if this matches the argument, false otherwise.
+ */
+ bool matches(const Hop &hop) const;
+
+ /**
+ * Returns a string representation of this that can be debugged but not parsed.
+ *
+ * @return The debug string.
+ */
+ string toDebugString() const;
+
+ /**
+ * Returns a string representation of this that can be parsed.
+ *
+ * @return The parseable string.
+ */
+ string toString() const;
+
+ /**
+ * Returns a string concatenation of a subset of the selector primitives contained in this.
+ *
+ * @param fromIncluding The index of the first primitive to include.
+ * @param toNotIncluding The index after the last primitive to include.
+ * @return The string concatenation.
+ */
+ string toString(uint32_t fromIncluding, uint32_t toNotIncluding) const;
+
+ /**
+ * Returns the prefix of this hop's selector to, but not including, the given index.
+ *
+ * @param toNotIncluding The index to which to generate prefix.
+ * @return The prefix before the index.
+ */
+ string getPrefix(uint32_t toNotIncluding) const;
+
+ /**
+ * Returns the suffix of this hop's selector from, but not including, the given index.
+ *
+ * @param fromNotIncluding The index from which to generate suffix.
+ * @return The suffix after the index.
+ */
+ string getSuffix(uint32_t fromNotIncluding) const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/hopblueprint.cpp b/messagebus/src/vespa/messagebus/routing/hopblueprint.cpp
new file mode 100644
index 00000000000..efcea51f58e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/hopblueprint.cpp
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".hopblueprint");
+
+#include "hopblueprint.h"
+#include "hopspec.h"
+
+namespace mbus {
+
+HopBlueprint::HopBlueprint(const HopSpec &spec) :
+ _selector(),
+ _recipients(),
+ _ignoreResult(spec.getIgnoreResult())
+{
+ Hop hop = Hop::parse(spec.getSelector());
+ for (uint32_t i = 0; i < hop.getNumDirectives(); ++i) {
+ _selector.push_back(hop.getDirective(i));
+ }
+ std::vector<string> lst;
+ for (uint32_t i = 0; i < spec.getNumRecipients(); ++i) {
+ lst.push_back(spec.getRecipient(i));
+ }
+ for (std::vector<string>::iterator it = lst.begin();
+ it != lst.end(); ++it)
+ {
+ _recipients.push_back(Hop::parse(*it));
+ }
+}
+
+HopBlueprint &
+HopBlueprint::setIgnoreResult(bool ignoreResult)
+{
+ _ignoreResult = ignoreResult;
+ return *this;
+}
+
+string
+HopBlueprint::toString() const
+{
+ string ret = "HopBlueprint(selector = { ";
+ for (uint32_t i = 0; i < _selector.size(); ++i) {
+ ret.append("'");
+ ret.append(_selector[i]->toString());
+ ret.append("'");
+ if (i < _selector.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" }, recipients = { ");
+ for (uint32_t i = 0; i < _recipients.size(); ++i) {
+ ret.append("'");
+ ret.append(_recipients[i].toString());
+ ret.append("'");
+ if (i < _recipients.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" }, ignoreResult = ");
+ ret.append(_ignoreResult ? "true" : "false");
+ ret.append(")");
+ return ret;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/hopblueprint.h b/messagebus/src/vespa/messagebus/routing/hopblueprint.h
new file mode 100644
index 00000000000..b37796fba5d
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/hopblueprint.h
@@ -0,0 +1,103 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "hop.h"
+
+namespace mbus {
+
+class HopSpec;
+
+/**
+ * A hop blueprint is a stored prototype of a hop that has been created from a {@link HopSpec} object. A map of these
+ * are stored in a {@link RoutingTable}.
+ */
+class HopBlueprint {
+private:
+ std::vector<IHopDirective::SP> _selector;
+ std::vector<Hop> _recipients;
+ bool _ignoreResult;
+
+public:
+ /**
+ * Create a new blueprint from a specification object.
+ *
+ * @param spec The specification to base instantiation on.
+ */
+ HopBlueprint(const HopSpec &spec);
+
+ /**
+ * Creates a hop instance from thie blueprint.
+ *
+ * @return The created hop.
+ */
+ Hop::UP create() const { return Hop::UP(new Hop(_selector, _ignoreResult)); }
+
+ /**
+ * Returns whether or not there are any directives contained in this hop.
+ *
+ * @return True if there is at least one directive.
+ */
+ bool hasDirectives() const { return !_selector.empty(); }
+
+ /**
+ * Returns the number of directives contained in this hop.
+ *
+ * @return The number of directives.
+ */
+ uint32_t getNumDirectives() const { return _selector.size(); }
+
+ /**
+ * Returns the directive at the given index.
+ *
+ * @param i The index of the directive to return.
+ * @return The item.
+ */
+ IHopDirective::SP getDirective(uint32_t i) const { return _selector[i]; }
+
+ /**
+ * Returns whether or not there are any recipients that the selector can choose from.
+ *
+ * @return True if there is at least one recipient.
+ */
+ bool hasRecipients() const { return !_recipients.empty(); }
+
+ /**
+ * Returns the number of recipients that the selector can choose from.
+ *
+ * @return The number of recipients.
+ */
+ uint32_t getNumRecipients() const { return _recipients.size(); }
+
+ /**
+ * Returns the recipient at the given index.
+ *
+ * @param i The index of the recipient to return.
+ * @return The recipient at the given index.
+ */
+ const Hop &getRecipient(uint32_t i) const { return _recipients[i]; }
+
+ /**
+ * Returns whether or not to ignore the result when routing through this hop.
+ *
+ * @return True to ignore the result.
+ */
+ bool getIgnoreResult() const { return _ignoreResult; }
+
+ /**
+ * Sets whether or not to ignore the result when routing through this hop.
+ *
+ * @param ignoreResult Whether or not to ignore the result.
+ * @return This, to allow chaining.
+ */
+ HopBlueprint &setIgnoreResult(bool ignoreResult);
+
+ /**
+ * Returns a string representation of this.
+ *
+ * @return The string.
+ */
+ string toString() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/hopspec.cpp b/messagebus/src/vespa/messagebus/routing/hopspec.cpp
new file mode 100644
index 00000000000..40c523ca1b6
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/hopspec.cpp
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".hopspec");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "routingspec.h"
+
+namespace mbus {
+
+HopSpec::HopSpec(const string &name, const string &selector) :
+ _name(name),
+ _selector(selector),
+ _recipients(),
+ _ignoreResult(false)
+{
+ // empty
+}
+
+HopSpec &
+HopSpec::addRecipients(const std::vector<string> &recipients)
+{
+ _recipients.insert(_recipients.end(), recipients.begin(), recipients.end());
+ return *this;
+}
+
+string
+HopSpec::removeRecipient(uint32_t i)
+{
+ string ret = _recipients[i];
+ _recipients.erase(_recipients.begin() + i);
+ return ret;
+}
+
+HopSpec &
+HopSpec::setIgnoreResult(bool ignoreResult)
+{
+ _ignoreResult = ignoreResult;
+ return *this;
+}
+
+void
+HopSpec::toConfig(string &cfg, const string &prefix) const
+{
+ cfg.append(prefix).append("name ").append(RoutingSpec::toConfigString(_name)).append("\n");
+ cfg.append(prefix).append("selector ").append(RoutingSpec::toConfigString(_selector)).append("\n");
+ if (_ignoreResult) {
+ cfg.append(prefix).append("ignoreresult true\n");
+ }
+ uint32_t numRecipients = _recipients.size();
+ if (numRecipients > 0) {
+ cfg.append(prefix).append("recipient[").append(vespalib::make_vespa_string("%d", numRecipients)).append("]\n");
+ for (uint32_t i = 0; i < numRecipients; ++i) {
+ cfg.append(prefix).append("recipient[").append(vespalib::make_vespa_string("%d", i)).append("] ");
+ cfg.append(RoutingSpec::toConfigString(_recipients[i])).append("\n");
+ }
+ }
+}
+
+string
+HopSpec::toString() const
+{
+ string ret = "";
+ toConfig(ret, "");
+ return ret;
+}
+
+bool
+HopSpec::operator==(const HopSpec &rhs) const
+{
+ if (_name != rhs._name) {
+ return false;
+ }
+ if (_selector != rhs._selector) {
+ return false;
+ }
+ if (_recipients.size() != rhs._recipients.size()) {
+ return false;
+ }
+ for (uint32_t i = 0, len = _recipients.size(); i < len; ++i) {
+ if (_recipients[i] != rhs._recipients[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/hopspec.h b/messagebus/src/vespa/messagebus/routing/hopspec.h
new file mode 100644
index 00000000000..3bb40847569
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/hopspec.h
@@ -0,0 +1,159 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vector>
+#include <vespa/messagebus/common.h>
+
+namespace mbus {
+
+/**
+ * Along with the {@link RoutingSpec}, {@link RoutingTableSpec} and {@link RouteSpec}, this holds the routing
+ * specifications for all protocols. The only way a client can configure or alter the settings of a message bus instance
+ * is through these classes.
+ *
+ * This class contains the spec for a single hop.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class HopSpec {
+private:
+ string _name;
+ string _selector;
+ std::vector<string> _recipients;
+ bool _ignoreResult;
+
+public:
+ /**
+ * The default constructor requires both the name and the selector.
+ *
+ * @param name A protocol unique name for this hop.
+ * @param selector A string that represents the selector for this hop.
+ */
+ HopSpec(const string &name, const string &selector);
+
+ /**
+ * Returns the protocol-unique name of this hop.
+ *
+ * @return The name.
+ */
+ const string &getName() const { return _name; }
+
+ /**
+ * Returns the string selector that resolves the recipients of this hop.
+ *
+ * @return The selector.
+ */
+ const string &getSelector() const { return _selector; }
+
+ /**
+ * Returns whether or not there are any recipients that the selector can choose from.
+ *
+ * @return True if there is at least one recipient.
+ */
+ bool hasRecipients() const { return !_recipients.empty(); }
+
+ /**
+ * Returns the number of recipients that the selector can choose from.
+ *
+ * @return The number of recipients.
+ */
+ uint32_t getNumRecipients() const { return _recipients.size(); }
+
+ /**
+ * Returns the recipients at the given index.
+ *
+ * @param i The index of the recipient to return.
+ * @return The recipient at the given index.
+ */
+ const string &getRecipient(uint32_t i) const { return _recipients[i]; }
+
+ /**
+ * Adds the given recipient to this.
+ *
+ * @param recipient The recipient to add.
+ * @return This, to allow chaining.
+ */
+ HopSpec &addRecipient(const string &recipient) { _recipients.push_back(recipient); return *this; }
+
+ /**
+ * Adds the given recipients to this.
+ *
+ * @param recipients The recipients to add.
+ * @return This, to allow chaining.
+ */
+ HopSpec &addRecipients(const std::vector<string> &recipients);
+
+ /**
+ * Sets the recipient at the given index.
+ *
+ * @param i The index at which to set the recipient.
+ * @param recipient The recipient to set.
+ * @return This, to allow chaining.
+ */
+ HopSpec &setRecipient(uint32_t i, const string &recipient) { _recipients[i] = recipient; return *this; }
+
+ /**
+ * Removes the recipient at the given index.
+ *
+ * @param i The index of the recipient to remove.
+ * @return The removed recipient.
+ */
+ string removeRecipient(uint32_t i);
+
+ /**
+ * Clears the list of recipients that the selector may choose from.
+ *
+ * @return This, to allow chaining.
+ */
+ HopSpec &clearRecipients() { _recipients.clear(); return *this; }
+
+ /**
+ * Returns whether or not to ignore the result when routing through this hop.
+ *
+ * @return True to ignore the result.
+ */
+ bool getIgnoreResult() const { return _ignoreResult; }
+
+ /**
+ * Sets whether or not to ignore the result when routing through this hop.
+ *
+ * @param ignoreResult Whether or not to ignore the result.
+ * @return This, to allow chaining.
+ */
+ HopSpec &setIgnoreResult(bool ignoreResult);
+
+ /**
+ * Appends the content of this to the given config string.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ void toConfig(string &cfg, const string &prefix) const;
+
+ /**
+ * Returns a string representation of this.
+ *
+ * @return The string.
+ */
+ string toString() const;
+
+ /**
+ * Implements the equality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this equals the other.
+ */
+ bool operator==(const HopSpec &rhs) const;
+
+ /**
+ * Implements the inequality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this does not equals the other.
+ */
+ bool operator!=(const HopSpec &rhs) const { return !(*this == rhs); }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/ihopdirective.h b/messagebus/src/vespa/messagebus/routing/ihopdirective.h
new file mode 100644
index 00000000000..6d6c59509d9
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/ihopdirective.h
@@ -0,0 +1,71 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/messagebus/common.h>
+
+namespace mbus {
+
+/**
+ * This class is the base class for the primitives that make up a {@link Hop}'s selector.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class IHopDirective {
+public:
+ /**
+ * Defines the various polymorphic variants of a hop directive.
+ */
+ enum Type {
+ TYPE_ERROR,
+ TYPE_POLICY,
+ TYPE_ROUTE,
+ TYPE_TCP,
+ TYPE_VERBATIM
+ };
+
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<IHopDirective> UP;
+ typedef std::shared_ptr<IHopDirective> SP;
+
+ /**
+ * Implements a virtual destructor to allow virtual methods.
+ */
+ virtual ~IHopDirective() {
+ // empty
+ }
+
+ /**
+ * Returns the type of directive that this is.
+ *
+ * @return The type.
+ */
+ virtual Type getType() const = 0;
+
+ /**
+ * Returns true if this directive matches another.
+ *
+ * @param dir The directive to compare this to.
+ * @return True if this matches the argument.
+ */
+ virtual bool matches(const IHopDirective &dir) const = 0;
+
+ /**
+ * Returns a string representation of this that can be parsed.
+ *
+ * @return The string.
+ */
+ virtual string toString() const = 0;
+
+ /**
+ * Returns a string representation of this that can be debugged but not parsed.
+ *
+ * @return The debug string.
+ */
+ virtual string toDebugString() const = 0;
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/iretrypolicy.h b/messagebus/src/vespa/messagebus/routing/iretrypolicy.h
new file mode 100644
index 00000000000..3727724500c
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/iretrypolicy.h
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <stdint.h>
+
+namespace mbus {
+
+/**
+ * When a {@link com.yahoo.messagebus.Reply} containing errors is returned to a {@link
+ * com.yahoo.messagebus.MessageBus}, an object implementing this interface is consulted on whether or not to
+ * resend the corresponding {@link com.yahoo.messagebus.Message}. The policy is passed to the message bus at
+ * creation time using the {@link com.yahoo.messagebus.MessageBusParams#setRetryPolicy(RetryPolicy::UP)}
+ * method.
+ */
+class IRetryPolicy {
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<IRetryPolicy> UP;
+ typedef std::shared_ptr<IRetryPolicy> SP;
+
+ /**
+ * Virtual destructor required for inheritance.
+ */
+ virtual ~IRetryPolicy() { /* empty */ }
+
+ /**
+ * Returns whether or not a {@link com.yahoo.messagebus.Reply} containing an {@link
+ * com.yahoo.messagebus.Error} with the given error code can be retried. This method is invoked once for
+ * each error in a reply.
+ *
+ * @param errorCode The code to check.
+ * @return True if the message can be resent.
+ */
+ virtual bool canRetry(uint32_t errorCode) const = 0;
+
+ /**
+ * Returns the number of seconds to delay resending a message.
+ *
+ * @param retry The retry attempt.
+ * @return The delay in seconds.
+ */
+ virtual double getRetryDelay(uint32_t retry) const = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/iroutingpolicy.h b/messagebus/src/vespa/messagebus/routing/iroutingpolicy.h
new file mode 100644
index 00000000000..8fabd5210c3
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/iroutingpolicy.h
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+
+namespace mbus {
+
+class RoutingContext;
+
+/**
+ * A routing policy selects who should get a message and merges the replies that are returned from those
+ * recipients. Note that recipient selection is done separately for each path element. This means that a routing policy
+ * is not selecting recipients directly, but rather the set of strings that should be used for the next service name
+ * path element. This process is done recursively until a set of actual service names are produced. The merging process
+ * is similar, but in reverse order.
+ */
+class IRoutingPolicy {
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<IRoutingPolicy> UP;
+ typedef std::shared_ptr<IRoutingPolicy> SP;
+
+public:
+ /**
+ * Destructor. Frees any allocated resources.
+ */
+ virtual ~IRoutingPolicy() {
+ // empty
+ }
+
+ /**
+ * This function must choose a set of services that is to receive the given message from a list of possible
+ * recipients. This is done by adding child routing contexts to the argument object. These children can then be
+ * iterated and manipulated even before selection pass is concluded.
+ *
+ * @param context The complete context for the invokation of this policy. Contains all available data.
+ */
+ virtual void select(RoutingContext &context) = 0;
+
+ /**
+ * This function is called when all replies have arrived for some message. The implementation is responsible for
+ * merging multiple replies into a single sensible reply. The replies is contained in the child context objects of
+ * the argument context, and then response must be set in that context.
+ *
+ * @param context The complete context for the invokation of this policy. Contains all available data.
+ */
+ virtual void merge(RoutingContext &context) = 0;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/policydirective.cpp b/messagebus/src/vespa/messagebus/routing/policydirective.cpp
new file mode 100644
index 00000000000..da278315631
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/policydirective.cpp
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "policydirective.h"
+
+namespace mbus {
+
+PolicyDirective::PolicyDirective(const vespalib::stringref &name, const vespalib::stringref &param) :
+ _name(name),
+ _param(param)
+{
+ // empty
+}
+
+string
+PolicyDirective::toString() const
+{
+ if (_param.empty()) {
+ return vespalib::make_vespa_string("[%s]", _name.c_str());
+ }
+ return vespalib::make_vespa_string("[%s:%s]", _name.c_str(), _param.c_str());
+}
+
+string
+PolicyDirective::toDebugString() const
+{
+ return vespalib::make_vespa_string("PolicyDirective(name = '%s', param = '%s')",
+ _name.c_str(), _param.c_str());
+}
+
+} // mbus
diff --git a/messagebus/src/vespa/messagebus/routing/policydirective.h b/messagebus/src/vespa/messagebus/routing/policydirective.h
new file mode 100644
index 00000000000..3b25bd76197
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/policydirective.h
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "ihopdirective.h"
+
+namespace mbus {
+
+/**
+ * This class represents a policy directive within a {@link Hop}'s selector. This means to create the named protocol
+ * using the given parameter string, and the running that protocol within the context of this directive.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class PolicyDirective : public IHopDirective {
+private:
+ string _name;
+ string _param;
+
+public:
+
+ /**
+ * Constructs a new policy selector item.
+ *
+ * @param name The name of the policy to invoke.
+ * @param param The parameter to pass to the name constructor.
+ */
+ PolicyDirective(const vespalib::stringref &name, const vespalib::stringref &param);
+
+ /**
+ * Returns the name of the policy that this item is to invoke.
+ *
+ * @return The name name.
+ */
+ const string &getName() const { return _name; }
+
+ /**
+ * Returns the parameter string for this policy directive.
+ *
+ * @return The parameter.
+ */
+ const string &getParam() const { return _param; }
+
+ virtual Type getType() const { return TYPE_POLICY; }
+ virtual bool matches(const IHopDirective &) const { return true; }
+ virtual string toString() const;
+ virtual string toDebugString() const;
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/resender.cpp b/messagebus/src/vespa/messagebus/routing/resender.cpp
new file mode 100644
index 00000000000..b767252f39f
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/resender.cpp
@@ -0,0 +1,99 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".resender");
+
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/tracelevel.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "resender.h"
+#include "routingnode.h"
+
+namespace mbus {
+
+Resender::Resender(IRetryPolicy::SP retryPolicy) :
+ _queue(),
+ _retryPolicy(retryPolicy),
+ _time()
+{
+ _time.SetNow();
+}
+
+Resender::~Resender()
+{
+ while (!_queue.empty()) {
+ _queue.top().second->discard();
+ _queue.pop();
+ }
+}
+
+void
+Resender::resendScheduled()
+{
+ typedef std::vector<RoutingNode*> NodeList;
+ NodeList sendList;
+
+ double now = _time.MilliSecsToNow();
+ while (!_queue.empty() && _queue.top().first <= now) {
+ sendList.push_back(_queue.top().second);
+ _queue.pop();
+ }
+
+ for (NodeList::iterator it = sendList.begin();
+ it != sendList.end(); ++it)
+ {
+ (*it)->getTrace().trace(mbus::TraceLevel::COMPONENT,
+ "Resender resending message.");
+ (*it)->send();
+ }
+}
+
+bool
+Resender::canRetry(uint32_t errorCode) const
+{
+ return _retryPolicy->canRetry(errorCode);
+}
+
+bool
+Resender::shouldRetry(const Reply &reply) const
+{
+ uint32_t numErrors = reply.getNumErrors();
+ if (numErrors == 0) {
+ return false;
+ }
+ for (uint32_t i = 0; i < numErrors; ++i) {
+ if (!_retryPolicy->canRetry(reply.getError(i).getCode())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+Resender::scheduleRetry(RoutingNode &node)
+{
+ Message &msg = node.getMessage();
+ if (!msg.getRetryEnabled()) {
+ return false;
+ }
+ uint32_t retry = msg.getRetry() + 1;
+ double delay = node.getReplyRef().getRetryDelay();
+ if (delay < 0) {
+ delay = _retryPolicy->getRetryDelay(retry);
+ }
+ if (msg.getTimeRemainingNow() * 0.001 - delay <= 0) {
+ node.addError(ErrorCode::TIMEOUT, "Timeout exceeded by resender, giving up.");
+ return false;
+ }
+ node.prepareForRetry(); // consumes the reply
+ node.getTrace().trace(
+ TraceLevel::COMPONENT,
+ vespalib::make_vespa_string("Message scheduled for retry %u in %.2f seconds.",
+ retry, delay));
+ msg.setRetry(retry);
+ _queue.push(Entry((uint64_t)(_time.MilliSecsToNow() + delay * 1000), &node));
+ return true;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/resender.h b/messagebus/src/vespa/messagebus/routing/resender.h
new file mode 100644
index 00000000000..43f195dae09
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/resender.h
@@ -0,0 +1,92 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/messagebus/queue.h>
+#include <vespa/messagebus/reply.h>
+#include <queue>
+#include <vector>
+#include <vespa/vespalib/util/sync.h>
+#include "iretrypolicy.h"
+
+namespace mbus {
+
+class RoutingNode;
+
+/**
+ * The resender handles scheduling and execution of sending instances of {@link
+ * RoutingNode}. An instance of this class is owned by {@link
+ * com.yahoo.messagebus.MessageBus}. Because this class does not have any
+ * internal thread, it depends on message bus to keep polling it whenever it has
+ * time.
+ */
+class Resender : public boost::noncopyable
+{
+private:
+ typedef std::pair<uint64_t, RoutingNode*> Entry;
+ struct Cmp {
+ bool operator()(const Entry &a, const Entry &b) {
+ return (b.first < a.first);
+ }
+ };
+ typedef std::priority_queue<Entry, std::vector<Entry>, Cmp> PriorityQueue;
+
+ PriorityQueue _queue;
+ IRetryPolicy::SP _retryPolicy;
+ FastOS_Time _time;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<Resender> UP;
+
+ /**
+ * Constructs a new resender.
+ *
+ * @param retryPolicy The retry policy to use.
+ */
+ Resender(IRetryPolicy::SP retryPolicy);
+
+ /**
+ * Empties the retry queue.
+ */
+ ~Resender();
+
+ /**
+ * Returns whether or not the current {@link RetryPolicy} supports resending
+ * a {@link Reply} that contains an error with the given error code.
+ *
+ * @param errorCode The code to check.
+ * @return True if the message can be resent.
+ */
+ bool canRetry(uint32_t errorCode) const;
+
+ /**
+ * Returns whether or not the given reply should be retried.
+ *
+ * @param reply The reply to check.
+ * @return True if retry is required.
+ */
+ bool shouldRetry(const Reply &reply) const;
+
+ /**
+ * Schedules the given node for resending, if enabled by message. This will
+ * invoke {@link RoutingNode#prepareForRetry()} if the node was queued. This
+ * method is NOT thread-safe, and should only be called by the messenger
+ * thread.
+ *
+ * @param node The node to resend.
+ * @return True if the node was queued.
+ */
+ bool scheduleRetry(RoutingNode &node);
+
+ /**
+ * Invokes {@link RoutingNode#send()} on all routing nodes that are
+ * applicable for sending at the current time.
+ */
+ void resendScheduled();
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.cpp b/messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.cpp
new file mode 100644
index 00000000000..d38a22c5893
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.cpp
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/messagebus/errorcode.h>
+#include "retrytransienterrorspolicy.h"
+
+namespace mbus {
+
+RetryTransientErrorsPolicy::RetryTransientErrorsPolicy() :
+ _enabled(true),
+ _baseDelay(1)
+{
+ // empty
+}
+
+RetryTransientErrorsPolicy &
+RetryTransientErrorsPolicy::setEnabled(bool enabled)
+{
+ _enabled = enabled;
+ return *this;
+}
+
+RetryTransientErrorsPolicy &
+RetryTransientErrorsPolicy::setBaseDelay(double baseDelay)
+{
+ _baseDelay = baseDelay;
+ return *this;
+}
+
+bool
+RetryTransientErrorsPolicy::canRetry(uint32_t errorCode) const
+{
+ return _enabled && errorCode < ErrorCode::FATAL_ERROR;
+}
+
+double
+RetryTransientErrorsPolicy::getRetryDelay(uint32_t retry) const
+{
+ return _baseDelay * retry;
+}
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.h b/messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.h
new file mode 100644
index 00000000000..98efe3de08c
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/retrytransienterrorspolicy.h
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "iretrypolicy.h"
+
+namespace mbus {
+
+/**
+ * Implements a retry policy that allows resending of any error that is not fatal. It also does progressive
+ * back-off, delaying each attempt by the given time multiplied by the retry attempt.
+ */
+class RetryTransientErrorsPolicy : public IRetryPolicy {
+private:
+ volatile bool _enabled;
+ volatile double _baseDelay;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<RetryTransientErrorsPolicy> UP;
+ typedef std::shared_ptr<RetryTransientErrorsPolicy> SP;
+
+ /**
+ * Constructs a new instance of this policy. By default retries are enabled with a 1.0 second base delay.
+ */
+ RetryTransientErrorsPolicy();
+
+ /**
+ * Sets whether or not this policy should allow retries or not.
+ *
+ * @param enabled True to allow retries.
+ * @return This, to allow chaining.
+ */
+ RetryTransientErrorsPolicy &setEnabled(bool enabled);
+
+ /**
+ * Sets the base delay in seconds to wait between retries. This amount is multiplied by the retry number.
+ *
+ * @param baseDelay The time in seconds.
+ * @return This, to allow chaining.
+ */
+ RetryTransientErrorsPolicy &setBaseDelay(double baseDelay);
+
+ // Implements IRetryPolicy.
+ bool canRetry(uint32_t errorCode) const;
+
+ // Implements IRetryPolicy.
+ double getRetryDelay(uint32_t retry) const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/route.cpp b/messagebus/src/vespa/messagebus/routing/route.cpp
new file mode 100644
index 00000000000..593a2a3f0ef
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/route.cpp
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".route");
+
+#include "route.h"
+#include "routeparser.h"
+
+namespace mbus {
+
+Route::Route() :
+ _hops()
+{
+ // empty
+}
+
+Route::Route(const std::vector<Hop> &lst) :
+ _hops(lst)
+{
+ // empty
+}
+
+Route &
+Route::addHop(const Hop &hop)
+{
+ _hops.push_back(hop);
+ return *this;
+}
+
+Route &
+Route::setHop(uint32_t i, const Hop &hop)
+{
+ _hops[i] = hop;
+ return *this;
+}
+
+Hop
+Route::removeHop(uint32_t i)
+{
+ Hop ret = _hops[i];
+ _hops.erase(_hops.begin() + i);
+ return ret;
+}
+
+Route &
+Route::clearHops()
+{
+ _hops.clear();
+ return *this;
+}
+
+string
+Route::toString() const {
+ string ret = "";
+ for (uint32_t i = 0; i < _hops.size(); ++i) {
+ ret.append(_hops[i].toString());
+ if (i < _hops.size() - 1) {
+ ret.append(" ");
+ }
+ }
+ return ret;
+}
+
+string
+Route::toDebugString() const {
+ string ret = "Route(hops = { ";
+ for (uint32_t i = 0; i < _hops.size(); ++i) {
+ ret.append(_hops[i].toDebugString());
+ if (i < _hops.size() - 1) {
+ ret.append(", ");
+ }
+ }
+ ret.append(" })");
+ return ret;
+}
+
+Route
+Route::parse(const string &route)
+{
+ return RouteParser::createRoute(route);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/route.h b/messagebus/src/vespa/messagebus/routing/route.h
new file mode 100644
index 00000000000..d027f28302c
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/route.h
@@ -0,0 +1,128 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vector>
+#include "hop.h"
+
+namespace mbus {
+
+class Hop;
+
+/**
+ * A route is a list of {@link Hop hops} that are resolved from first to last as a routable moves from source to
+ * destination. A route may be changed at any time be either application logic or an invoked {@link RoutingPolicy}, so
+ * no guarantees on actual path can be given without the full knowledge of all that logic.
+ *
+ * To construct a route you may either use the factory method {@link this#parse(String)} to produce a route instance
+ * from a string representation, or you may build one programatically through the hop accessors.
+ */
+class Route {
+private:
+ std::vector<Hop> _hops;
+
+public:
+ /**
+ * Convenience typedef for an auto-pointer to a route.
+ */
+ typedef std::unique_ptr<Route> UP;
+
+ /**
+ * Parses the given string as a list of space-separated hops. The {@link this#toString()} method is compatible with
+ * this parser.
+ *
+ * @param route The string to parse.
+ * @return A route that corresponds to the string.
+ */
+ static Route parse(const string &route);
+
+ /**
+ * Create a Route that contains no hops
+ */
+ Route();
+
+ /**
+ * Constructs a route that contains the given hops.
+ *
+ * @param hops The hops to instantiate with.
+ */
+ Route(const std::vector<Hop> &hops);
+
+ /**
+ * Returns whether or not there are any hops in this route.
+ *
+ * @return True if there is at least one hop.
+ */
+ bool hasHops() const { return !_hops.empty(); }
+
+ /**
+ * Returns the number of hops that make up this route.
+ *
+ * @return The number of hops.
+ */
+ uint32_t getNumHops() const { return _hops.size(); }
+
+ /**
+ * Returns the hop at the given index.
+ *
+ * @param i The index of the hop to return.
+ * @return The hop.
+ */
+ Hop &getHop(uint32_t i) { return _hops[i]; }
+
+ /**
+ * Returns a const reference to the hop at the given index.
+ *
+ * @param i The index of the hop to return.
+ * @return The hop.
+ */
+ const Hop &getHop(uint32_t i) const { return _hops[i]; }
+
+ /**
+ * Adds a hop to the list of hops that make up this route.
+ *
+ * @param hop The hop to add.
+ * @return This, to allow chaining.
+ */
+ Route &addHop(const Hop &hop);
+
+ /**
+ * Sets the hop at a given index.
+ *
+ * @param i The index at which to set the hop.
+ * @param hop The hop to set.
+ * @return This, to allow chaining.
+ */
+ Route &setHop(uint32_t i, const Hop &hop);
+
+ /**
+ * Removes the hop at a given index.
+ *
+ * @param i The index of the hop to remove.
+ * @return The hop removed.
+ */
+ Hop removeHop(uint32_t i);
+
+ /**
+ * Clears the list of hops that make up this route.
+ *
+ * @return This, to allow chaining.
+ */
+ Route &clearHops();
+
+ /**
+ * Returns a string representation of this route.
+ *
+ * @return A string representation.
+ */
+ string toString() const;
+
+ /**
+ * Returns a string representation of this that can be debugged but not parsed.
+ *
+ * @return The debug string.
+ */
+ string toDebugString() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routedirective.cpp b/messagebus/src/vespa/messagebus/routing/routedirective.cpp
new file mode 100644
index 00000000000..b0389904a9a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routedirective.cpp
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "routedirective.h"
+
+namespace mbus {
+
+RouteDirective::RouteDirective(const vespalib::stringref & name) :
+ _name(name)
+{
+ // empty
+}
+
+bool
+RouteDirective::matches(const IHopDirective &dir) const
+{
+ if (dir.getType() != TYPE_ROUTE) {
+ return false;
+ }
+ return _name == static_cast<const RouteDirective&>(dir).getName();
+}
+
+string
+RouteDirective::toString() const
+{
+ return vespalib::make_vespa_string("route:%s", _name.c_str());
+}
+
+string
+RouteDirective::toDebugString() const
+{
+ return vespalib::make_vespa_string("RouteDirective(name = '%s')",
+ _name.c_str());
+}
+
+} // mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routedirective.h b/messagebus/src/vespa/messagebus/routing/routedirective.h
new file mode 100644
index 00000000000..3fe50e2efd9
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routedirective.h
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "ihopdirective.h"
+
+namespace mbus {
+
+/**
+ * This class represents a route directive within a {@link Hop}'s selector. This will be replaced by the named route
+ * when evaluated. If the route is not present in the running protocol's routing table, routing will fail.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class RouteDirective : public IHopDirective {
+private:
+ string _name;
+
+public:
+
+ /**
+ * Constructs a new directive to insert a route.
+ *
+ * @param name The name of the route to insert.
+ */
+ RouteDirective(const vespalib::stringref &name);
+
+ /**
+ * Returns the name of the route to insert.
+ *
+ * @return The name name.
+ */
+ const string &getName() const { return _name; }
+
+ virtual Type getType() const { return TYPE_ROUTE; }
+ virtual bool matches(const IHopDirective &dir) const;
+ virtual string toString() const;
+ virtual string toDebugString() const;
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routeparser.cpp b/messagebus/src/vespa/messagebus/routing/routeparser.cpp
new file mode 100644
index 00000000000..c1c0dba871e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routeparser.cpp
@@ -0,0 +1,153 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routeparser");
+
+#include <vespa/messagebus/common.h>
+#include <stdio.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "errordirective.h"
+#include "policydirective.h"
+#include "routedirective.h"
+#include "routeparser.h"
+#include "tcpdirective.h"
+#include "verbatimdirective.h"
+
+using vespalib::stringref;
+
+namespace mbus {
+
+bool
+RouteParser::isWhitespace(char c)
+{
+ return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t';
+}
+
+IHopDirective::SP
+RouteParser::createRouteDirective(const stringref &str)
+{
+ return IHopDirective::SP(new RouteDirective(str));
+}
+
+IHopDirective::SP
+RouteParser::createTcpDirective(const stringref &str)
+{
+ size_t posP = str.find(":");
+ if (posP == string::npos || posP == 0) {
+ return IHopDirective::SP(); // no host
+ }
+ size_t posS = str.find("/", posP);
+ if (posS == string::npos || posS == posP + 1) {
+ return IHopDirective::SP(); // no port
+ }
+ return IHopDirective::SP(new TcpDirective(str.substr(0, posP),
+ atoi(str.substr(posP + 1, posS - 1).c_str()),
+ str.substr(posS + 1)));
+}
+
+IHopDirective::SP
+RouteParser::createPolicyDirective(const stringref &str)
+{
+ size_t pos = str.find(":");
+ if (pos == string::npos) {
+ return IHopDirective::SP(new PolicyDirective(str, ""));
+ }
+ return IHopDirective::SP(new PolicyDirective(str.substr(0, pos), str.substr(pos + 1)));
+}
+
+IHopDirective::SP
+RouteParser::createVerbatimDirective(const stringref &str)
+{
+ return IHopDirective::SP(new VerbatimDirective(str));
+}
+
+IHopDirective::SP
+RouteParser::createErrorDirective(const stringref &str)
+{
+ return IHopDirective::SP(new ErrorDirective(str));
+}
+
+IHopDirective::SP
+RouteParser::createDirective(const stringref &str)
+{
+ if (str.size() > 2 && str[0] == '[') {
+ return createPolicyDirective(str.substr(1, str.size() - 2));
+ }
+ return createVerbatimDirective(str);
+}
+
+Hop
+RouteParser::createHop(const stringref &str)
+{
+ if (str.empty()) {
+ return Hop().addDirective(createErrorDirective("Failed to parse empty string."));
+ }
+ size_t len = str.size();
+ if (len > 1 && str[0] == '?') {
+ Hop hop = createHop(str.substr(1));
+ hop.setIgnoreResult(true);
+ return hop;
+ }
+ if (len > 4 && str.substr(0, 4) == "tcp/") {
+ IHopDirective::SP tcp = createTcpDirective(str.substr(4));
+ if (tcp.get() != NULL) {
+ return Hop().addDirective(tcp);
+ }
+ }
+ if (len > 6 && str.substr(0, 6) == "route:") {
+ return Hop().addDirective(createRouteDirective(str.substr(6)));
+ }
+ Hop ret;
+ for (size_t from = 0, at = 0, depth = 0; at <= len; ++at) {
+ if (at == len || (depth == 0 && str[at] == '/')) {
+ if (depth > 0) {
+ return Hop().addDirective(createErrorDirective(
+ "Unexpected token '': syntax error"));
+ }
+ ret.addDirective(createDirective(str.substr(from, at - from)));
+ from = at + 1;
+ } else if (isWhitespace(str[at]) && depth == 0) {
+ return Hop().addDirective(createErrorDirective(
+ vespalib::make_string(
+ "Failed to completely parse '%s'.",
+ str.c_str())));
+ } else if (str[at] == '[') {
+ ++depth;
+ } else if (str[at] == ']') {
+ if (depth == 0) {
+ return Hop().addDirective(createErrorDirective(
+ "Unexpected token ']': syntax error"));
+ }
+ --depth;
+ }
+ }
+ return ret;
+}
+
+Route
+RouteParser::createRoute(const stringref &str)
+{
+ Route ret;
+ for (size_t from = 0, at = 0, depth = 0; at <= str.size(); ++at) {
+ if (at == str.size() || (depth == 0 && isWhitespace(str[at]))) {
+ if (from < at - 1) {
+ Hop hop = createHop(str.substr(from, at - from));
+ if (hop.hasDirectives() &&
+ hop.getDirective(0)->getType() == IHopDirective::TYPE_ERROR)
+ {
+ return Route().addHop(hop);
+ }
+ ret.addHop(hop);
+ }
+ from = at + 1;
+ } else if (str[at] == '[') {
+ ++depth;
+ } else if (str[at] == ']') {
+ --depth;
+ }
+ }
+ return ret;
+}
+
+} // mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routeparser.h b/messagebus/src/vespa/messagebus/routing/routeparser.h
new file mode 100644
index 00000000000..4ccd59cbdc4
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routeparser.h
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/vespalib/util/sync.h>
+#include "hop.h"
+#include "route.h"
+
+namespace mbus {
+
+/**
+ * This is a convenient entry point into creating a route or hop object. You can
+ * either build a routing object by hand, but using the factory method {@link
+ * #create} with a string is far simpler.
+ */
+class RouteParser {
+private:
+ static bool isWhitespace(char c);
+ static IHopDirective::SP createDirective(const vespalib::stringref &str);
+ static IHopDirective::SP createErrorDirective(const vespalib::stringref &str);
+ static IHopDirective::SP createPolicyDirective(const vespalib::stringref &str);
+ static IHopDirective::SP createRouteDirective(const vespalib::stringref &str);
+ static IHopDirective::SP createTcpDirective(const vespalib::stringref &str);
+ static IHopDirective::SP createVerbatimDirective(const vespalib::stringref &str);
+
+public:
+ /**
+ * Creates a hop from a string representation.
+ *
+ * @param str The string to parse as a hop.
+ * @return The created hop.
+ */
+ static Hop createHop(const vespalib::stringref &str);
+
+ /**
+ * Creates a route from a string representation.
+ *
+ * @param str The string to parse as a route.
+ * @return The created route.
+ */
+ static Route createRoute(const vespalib::stringref &str);
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routespec.cpp b/messagebus/src/vespa/messagebus/routing/routespec.cpp
new file mode 100644
index 00000000000..848b553bf98
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routespec.cpp
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routespec");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "routingspec.h"
+
+namespace mbus {
+
+RouteSpec::RouteSpec(const string &name) :
+ _name(name),
+ _hops()
+{
+ // empty
+}
+
+RouteSpec &
+RouteSpec::addHops(const std::vector<string> &hops)
+{
+ _hops.insert(_hops.end(), hops.begin(), hops.end());
+ return *this;
+}
+
+string
+RouteSpec::removeHop(uint32_t i)
+{
+ string ret = _hops[i];
+ _hops.erase(_hops.begin() + i);
+ return ret;
+}
+
+void
+RouteSpec::toConfig(string &cfg, const string &prefix) const
+{
+ cfg.append(prefix).append("name ").append(RoutingSpec::toConfigString(_name)).append("\n");
+ uint32_t numHops = _hops.size();
+ if (numHops > 0) {
+ cfg.append(prefix).append("hop[").append(vespalib::make_vespa_string("%d", numHops)).append("]\n");
+ for (uint32_t i = 0; i < numHops; ++i) {
+ cfg.append(prefix).append("hop[").append(vespalib::make_vespa_string("%d", i)).append("] ");
+ cfg.append(RoutingSpec::toConfigString(_hops[i])).append("\n");
+ }
+ }
+}
+
+string
+RouteSpec::toString() const
+{
+ string ret = "";
+ toConfig(ret, "");
+ return ret;
+}
+
+bool
+RouteSpec::operator==(const RouteSpec &rhs) const
+{
+ if (_name != rhs._name) {
+ return false;
+ }
+ if (_hops.size() != rhs._hops.size()) {
+ return false;
+ }
+ for (uint32_t i = 0, len = _hops.size(); i < len; ++i) {
+ if (_hops[i] != rhs._hops[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routespec.h b/messagebus/src/vespa/messagebus/routing/routespec.h
new file mode 100644
index 00000000000..1eaefb11958
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routespec.h
@@ -0,0 +1,134 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace mbus {
+
+/**
+ * Along with the {@link RoutingSpec}, {@link RoutingTableSpec} and {@link HopSpec}, this holds the routing
+ * specifications for all protocols. The only way a client can configure or alter the settings of a message bus instance
+ * is through these classes.
+ *
+ * This class contains the spec for a single route.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class RouteSpec {
+private:
+ string _name;
+ std::vector<string> _hops;
+
+public:
+ /**
+ * The default constructor assigns a value to the immutable name variable.
+ *
+ * @param name A protocol-unique name for this route.
+ */
+ RouteSpec(const string &name);
+
+ /**
+ * Returns the protocol-unique name of this route.
+ *
+ * @return The name.
+ */
+ const string &getName() const { return _name; }
+
+ /**
+ * Returns the hop name at the given index.
+ *
+ * @param i The index of the hop to return.
+ * @return The hop at the given index.
+ */
+ const string &getHop(uint32_t i) const { return _hops[i]; }
+
+ /**
+ * Returns whether or not there are any hops in this route.
+ *
+ * @return True if there is at least one hop.
+ */
+ bool hasHops() const { return !_hops.empty(); }
+
+ /**
+ * Returns the number of hops that make up this route.
+ *
+ * @return The number of hops.
+ */
+ uint32_t getNumHops() const { return _hops.size(); }
+
+ /**
+ * Adds the given hop name to this.
+ *
+ * @param hop The hop to add.
+ * @return This, to allow chaining.
+ */
+ RouteSpec &addHop(const string &hop) { _hops.push_back(hop); return *this; }
+
+ /**
+ * Adds the given hop names to this.
+ *
+ * @param hops The hops to add.
+ * @return This, to allow chaining.
+ */
+ RouteSpec &addHops(const std::vector<string> &hops);
+
+ /**
+ * Sets the hop name for a given index.
+ *
+ * @param i The index of the hop to set.
+ * @param hop The hop to set.
+ * @return This, to allow chaining.
+ */
+ RouteSpec &setHop(uint32_t i, const string &hop) { _hops[i] = hop; return *this; }
+
+ /**
+ * Removes the hop name at the given index.
+ *
+ * @param i The index of the hop to remove.
+ * @return The removed hop.
+ */
+ string removeHop(uint32_t i);
+
+ /**
+ * Clears the list of hops that make up this route.
+ *
+ * @return This, to allow chaining.
+ */
+ RouteSpec &clearHops() { _hops.clear(); return *this; }
+
+ /**
+ * Appends the content of this to the given config string.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ void toConfig(string &cfg, const string &prefix) const;
+
+ /**
+ * Returns a string representation of this route specification.
+ *
+ * @return The string.
+ */
+ string toString() const;
+
+ /**
+ * Implements the equality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this equals the other.
+ */
+ bool operator==(const RouteSpec &rhs) const;
+
+ /**
+ * Implements the inequality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this does not equals the other.
+ */
+ bool operator!=(const RouteSpec &rhs) const { return !(*this == rhs); }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routingcontext.cpp b/messagebus/src/vespa/messagebus/routing/routingcontext.cpp
new file mode 100644
index 00000000000..8e7f2739148
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingcontext.cpp
@@ -0,0 +1,239 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routingcontext");
+
+#include "route.h"
+#include "routingnode.h"
+
+namespace mbus {
+
+RoutingContext::RoutingContext(RoutingNode &node, uint32_t directive) :
+ _node(node),
+ _directive(directive),
+ _consumableErrors(),
+ _selectOnRetry(true),
+ _context()
+{
+ // empty
+}
+
+bool
+RoutingContext::hasRecipients() const
+{
+ return !_node.getRecipients().empty();
+}
+
+uint32_t
+RoutingContext::getNumRecipients() const
+{
+ return _node.getRecipients().size();
+}
+
+const Route &
+RoutingContext::getRecipient(uint32_t idx) const
+{
+ return _node.getRecipients()[idx];
+}
+
+const std::vector<Route> &
+RoutingContext::getAllRecipients() const
+{
+ return _node.getRecipients();
+}
+
+void
+RoutingContext::getMatchedRecipients(std::vector<Route> &ret) const
+{
+ std::set<string> done;
+ const std::vector<Route> &recipients = _node.getRecipients();
+ const Hop &hop = getHop();
+ for (std::vector<Route>::const_iterator it = recipients.begin();
+ it != recipients.end(); ++it)
+ {
+ if (it->hasHops() && hop.matches(it->getHop(0))) {
+ IHopDirective::SP dir = it->getHop(0).getDirective(_directive);
+ string key = dir->toString();
+ if (done.find(key) == done.end()) {
+ Route add = *it;
+ add.setHop(0, hop);
+ add.getHop(0).setDirective(_directive, dir);
+ ret.push_back(add);
+ done.insert(key);
+ }
+ }
+ }
+}
+
+bool
+RoutingContext::getSelectOnRetry() const
+{
+ return _selectOnRetry;
+}
+
+RoutingContext &
+RoutingContext::setSelectOnRetry(bool selectOnRetry)
+{
+ _selectOnRetry = selectOnRetry;
+ return *this;
+}
+
+const Route &
+RoutingContext::getRoute() const
+{
+ return _node.getRoute();
+}
+
+const Hop &
+RoutingContext::getHop() const
+{
+ return _node.getRoute().getHop(0);
+}
+
+uint32_t
+RoutingContext::getDirectiveIndex() const
+{
+ return _directive;
+}
+
+const PolicyDirective &
+RoutingContext::getDirective() const
+{
+ return static_cast<const PolicyDirective&>(*getHop().getDirective(_directive));
+}
+
+string
+RoutingContext::getHopPrefix() const
+{
+ return getHop().getPrefix(_directive);
+}
+
+string
+RoutingContext::getHopSuffix() const
+{
+ return getHop().getSuffix(_directive);
+}
+
+Context &
+RoutingContext::getContext()
+{
+ return _context;
+}
+
+const Context &
+RoutingContext::getContext() const
+{
+ return _context;
+}
+
+RoutingContext &
+RoutingContext::setContext(const Context &ctx)
+{
+ _context = ctx;
+ return *this;
+}
+
+const Message &
+RoutingContext::getMessage() const
+{
+ return _node.getMessage();
+}
+
+void
+RoutingContext::trace(uint32_t level, const string &note)
+{
+ _node.getTrace().trace(level, note);
+}
+
+bool
+RoutingContext::hasReply() const
+{
+ return _node.hasReply();
+}
+
+const Reply &
+RoutingContext::getReply() const
+{
+ return _node.getReplyRef();
+}
+
+RoutingContext &
+RoutingContext::setReply(Reply::UP reply)
+{
+ _node.setReply(std::move(reply));
+ return *this;
+}
+
+RoutingContext &
+RoutingContext::setError(uint32_t code, const string &msg)
+{
+ _node.setError(code, msg);
+ return *this;
+}
+
+RoutingContext &
+RoutingContext::setError(const Error &err)
+{
+ _node.setError(err);
+ return *this;
+}
+
+MessageBus &
+RoutingContext::getMessageBus()
+{
+ return _node.getMessageBus();
+}
+
+bool
+RoutingContext::hasChildren() const
+{
+ return !_node.getChildren().empty();
+}
+
+uint32_t
+RoutingContext::getNumChildren() const
+{
+ return _node.getChildren().size();
+}
+
+RoutingNodeIterator
+RoutingContext::getChildIterator()
+{
+ return RoutingNodeIterator(_node.getChildren());
+}
+
+void
+RoutingContext::addChild(const Route &route)
+{
+ _node.addChild(route);
+}
+
+void
+RoutingContext::addChildren(const std::vector<Route> &routes)
+{
+ for (std::vector<Route>::const_iterator it = routes.begin();
+ it != routes.end(); ++it)
+ {
+ addChild(*it);
+ }
+}
+
+const slobrok::api::IMirrorAPI &
+RoutingContext::getMirror() const
+{
+ return _node.getNetwork().getMirror();
+}
+
+void
+RoutingContext::addConsumableError(uint32_t errorCode)
+{
+ _consumableErrors.insert(errorCode);
+}
+
+bool
+RoutingContext::isConsumableError(uint32_t errorCode)
+{
+ return _consumableErrors.find(errorCode) != _consumableErrors.end();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routingcontext.h b/messagebus/src/vespa/messagebus/routing/routingcontext.h
new file mode 100644
index 00000000000..b9c26dc6224
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingcontext.h
@@ -0,0 +1,291 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/messagebus/context.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/slobrok/sbmirror.h>
+#include <string>
+#include <set>
+#include "hop.h"
+#include "policydirective.h"
+#include "routingnodeiterator.h"
+
+namespace mbus {
+
+class RoutingNode;
+
+/**
+ * This context object is what is seen by {@link RoutingPolicy} when doing both select() and merge(). It
+ * contains the necessary accessors to everything a policy is expected to need. An instance of this is created
+ * for every {@link RoutingNode} that contains a policy.
+ */
+class RoutingContext {
+private:
+ RoutingNode &_node;
+ uint32_t _directive;
+ std::set<uint32_t> _consumableErrors;
+ bool _selectOnRetry;
+ Context _context;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<RoutingContext> UP;
+
+ /**
+ * Constructs a new routing context for a given routing node and hop.
+ *
+ * @param node The owning routing node.
+ * @param directive The index to the policy directive of the hop.
+ */
+ RoutingContext(RoutingNode &node, uint32_t directive);
+
+ /**
+ * Returns whether or not this hop has any configured recipients.
+ *
+ * @return True if there is at least one recipient.
+ */
+ bool hasRecipients() const;
+
+ /**
+ * Returns the number of configured recipients for this hop.
+ *
+ * @return The recipient count.
+ */
+ uint32_t getNumRecipients() const;
+
+ /**
+ * Returns the configured recipient at the given index.
+ *
+ * @param idx The index of the recipient to return.
+ * @return The reipient at the given index.
+ */
+ const Route &getRecipient(uint32_t idx) const;
+
+ /**
+ * Returns all configured recipients for this hop.
+ *
+ * @return An unmodifiable list of recipients.
+ */
+ const std::vector<Route> &getAllRecipients() const;
+
+ /**
+ * Returns a list of all configured recipients whose first hop matches this.
+ *
+ * @param ret The list to add matched recipients to.
+ */
+ void getMatchedRecipients(std::vector<Route> &ret) const;
+
+ /**
+ * Returns whether or not the policy is required to reselect if resending occurs.
+ *
+ * @return True to invoke {@link RoutingPolicy#select(RoutingContext)} on resend.
+ */
+ bool getSelectOnRetry() const;
+
+ /**
+ * Sets whether or not the policy is required to reselect if resending occurs.
+ *
+ * @param selectOnRetry The value to set.
+ * @return This, to allow chaining.
+ */
+ RoutingContext &setSelectOnRetry(bool selectOnRetry);
+
+ /**
+ * Returns the route that contains the routing policy that spawned this.
+ *
+ * @return The route.
+ */
+ const Route &getRoute() const;
+
+ /**
+ * Returns the hop that contains the routing policy that spawned this.
+ *
+ * @return The hop.
+ */
+ const Hop &getHop() const;
+
+ /**
+ * Returns the index of the hop directive that spawned this.
+ *
+ * @return The directive index.
+ */
+ uint32_t getDirectiveIndex() const;
+
+ /**
+ * Returns the policy directive that spawned this.
+ *
+ * @return The directive object.
+ */
+ const PolicyDirective &getDirective() const;
+
+ /**
+ * Returns the part of the route string that precedes the active policy directive. This is the same as calling
+ * {@link this#getHop()}.getPrefix({@link this#getDirectiveIndex()}).
+ *
+ * @return The hop prefix.
+ */
+ string getHopPrefix() const;
+
+ /**
+ * Returns the remainder of the route string immediately following the active policy directive. This is the same as
+ * calling {@link this#getHop()}.getSuffix({@link this#getDirectiveIndex()}).
+ *
+ * @return The hop suffix.
+ */
+ string getHopSuffix() const;
+
+ /**
+ * Returns the routing specific context object.
+ *
+ * @return The context.
+ */
+ Context &getContext();
+
+ /**
+ * Returns a const reference to the routing specific context object.
+ *
+ * @return The context.
+ */
+ const Context &getContext() const;
+
+ /**
+ * Sets a routing specific context object that will be available at merge().
+ *
+ * @param context An arbitrary object.
+ * @return This, to allow chaining.
+ */
+ RoutingContext &setContext(const Context &ctx);
+
+ /**
+ * Returns the message being routed.
+ *
+ * @return The message.
+ */
+ const Message &getMessage() const;
+
+ /**
+ * Adds a string to the trace of the message being routed.
+ *
+ * @param level The level of the trace note.
+ * @param note The note to add.
+ */
+ void trace(uint32_t level, const string &note);
+
+ /**
+ * Returns whether or not a reply is available.
+ *
+ * @return True if a reply is set.
+ */
+ bool hasReply() const;
+
+ /**
+ * Returns the reply generated by the associated routing policy.
+ *
+ * @return The reply.
+ */
+ const Reply &getReply() const;
+
+ /**
+ * Sets the reply generated by the associated routing policy.
+ *
+ * @param reply The reply to set.
+ * @return This, to allow chaining.
+ */
+ RoutingContext &setReply(Reply::UP reply);
+
+ /**
+ * This is a convenience method to call {@link #setError(Error)}.
+ *
+ * @param code The code of the error to set.
+ * @param msg The message of the error to set.
+ * @return This, to allow chaining.
+ */
+ RoutingContext &setError(uint32_t code, const string &msg);
+
+ /**
+ * This is a convenience method to assign an {@link EmptyReply} containing a single error to this. This also fiddles
+ * with the trace object so that the error gets written to it.
+ *
+ * @param err The error to set.
+ * @return This, to allow chaining.
+ * @see #setReply(Reply)
+ */
+ RoutingContext &setError(const Error &err);
+
+ /**
+ * Returns the message bus instance on which this is running.
+ *
+ * @return The message bus.
+ */
+ MessageBus &getMessageBus();
+
+ /**
+ * Returns whether or not the owning routing node has any child nodes.
+ *
+ * @return True if there is at least one child.
+ */
+ bool hasChildren() const;
+
+ /**
+ * Returns the number of children the owning routing node has.
+ *
+ * @return The child count.
+ */
+ uint32_t getNumChildren() const;
+
+ /**
+ * Returns an iterator for the child routing nodes.
+ *
+ * @return The iterator.
+ */
+ RoutingNodeIterator getChildIterator();
+
+ /**
+ * Adds a child routing context to this based on a given route. This is the typical entry point a policy will use to
+ * select recipients during a {@link RoutingPolicy#select(RoutingContext)} invokation.
+ *
+ * @param route The route to contain in the child context.
+ */
+ void addChild(const Route &route);
+
+ /**
+ * This is a convenience method to more easily add a list of children to this. It will simply call the {@link
+ * this#addChild} method for each element in the list.
+ *
+ * @param routes A list of routes to add as children.
+ */
+ void addChildren(const std::vector<Route> &routes);
+
+ /**
+ * Returns the local mirror of the system's name server.
+ *
+ * @return The mirror api.
+ */
+ const slobrok::api::IMirrorAPI &getMirror() const;
+
+ /**
+ * Adds the given error code to the list of codes that the associated routing policy <u>may</u> consume. This is
+ * used to verify whether or not a resolved routing tree can succeed if sent. Because verification is only done
+ * before sending, the error types that must be added here are only those that can be generated by message bus
+ * itself.
+ *
+ * @param errorCode The code that might be consumed.
+ * @see RoutingNode#hasUnconsumedErrors()
+ * @see com.yahoo.messagebus.ErrorCode
+ */
+ void addConsumableError(uint32_t errorCode);
+
+ /**
+ * Returns whether or not the given error code <u>may</u> be consumed by the associated routing policy.
+ *
+ * @param errorCode The code to check.
+ * @return True if the code may be consumed.
+ * @see this#addConsumableError(int)
+ */
+ bool isConsumableError(uint32_t errorCode);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routingnode.cpp b/messagebus/src/vespa/messagebus/routing/routingnode.cpp
new file mode 100644
index 00000000000..55b96837098
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingnode.cpp
@@ -0,0 +1,601 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routingnode");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/tracelevel.h>
+#include <stack>
+#include <vespa/vespalib/util/atomic.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "errordirective.h"
+#include "policydirective.h"
+#include "routedirective.h"
+#include "routingnode.h"
+
+namespace mbus {
+
+RoutingNode::RoutingNode(MessageBus &mbus, INetwork &net, Resender *resender,
+ IReplyHandler &replyHandler, Message &msg,
+ IDiscardHandler *discardHandler)
+ : _mbus(mbus),
+ _net(net),
+ _resender(resender),
+ _parent(NULL),
+ _recipients(),
+ _children(),
+ _replyHandler(&replyHandler),
+ _discardHandler(discardHandler),
+ _trace(msg.getTrace().getLevel()),
+ _pending(0),
+ _msg(msg),
+ _reply(),
+ _route(msg.getRoute()),
+ _policy(),
+ _routingContext(),
+ _serviceAddress(),
+ _isActive(true),
+ _shouldRetry(false)
+{
+ // empty
+}
+
+RoutingNode::RoutingNode(RoutingNode &parent, const Route &route)
+ : _mbus(parent._mbus),
+ _net(parent._net),
+ _resender(parent._resender),
+ _parent(&parent),
+ _recipients(parent._recipients),
+ _children(),
+ _replyHandler(NULL),
+ _discardHandler(NULL),
+ _trace(parent._trace.getLevel()),
+ _pending(0),
+ _msg(parent._msg),
+ _reply(),
+ _route(route),
+ _policy(),
+ _routingContext(),
+ _serviceAddress(),
+ _isActive(true),
+ _shouldRetry(false)
+{
+ // empty
+}
+
+RoutingNode::~RoutingNode()
+{
+ clearChildren();
+}
+
+void
+RoutingNode::clearChildren()
+{
+ for (std::vector<RoutingNode*>::iterator it = _children.begin();
+ it != _children.end(); ++it)
+ {
+ delete *it;
+ }
+ _children.clear();
+}
+
+void
+RoutingNode::discard()
+{
+ LOG_ASSERT(_parent == NULL);
+ if (_discardHandler != NULL) {
+ _discardHandler->handleDiscard(Context());
+ }
+}
+
+void
+RoutingNode::send()
+{
+ if (!resolve(0)) {
+ notifyAbort("Route resolution failed.");
+ } else if (hasUnconsumedErrors()) {
+ notifyAbort("Errors found while resolving route.");
+ } else {
+ notifyTransmit();
+ }
+}
+
+void
+RoutingNode::prepareForRetry()
+{
+ _shouldRetry = false;
+ _reply.reset();
+ if (_routingContext.get() != NULL && _routingContext->getSelectOnRetry()) {
+ clearChildren();
+ } else if (!_children.empty()) {
+ bool retryingSome = false;
+ for (std::vector<RoutingNode*>::iterator it = _children.begin();
+ it != _children.end(); ++it)
+ {
+ RoutingNode *child= *it;
+ if (child->_shouldRetry || child->_reply.get() == NULL) {
+ child->prepareForRetry();
+ retryingSome = true;
+ }
+ }
+ if (!retryingSome) {
+ // Entering here means we have no children that should be resent even
+ // though this node reports a transient error. The only thing we can
+ // do is to reselect from this.
+ clearChildren();
+ }
+ }
+}
+
+void
+RoutingNode::notifyParent()
+{
+ if (_serviceAddress.get() != NULL) {
+ _net.freeServiceAddress(*this);
+ }
+ tryIgnoreResult();
+ if (_parent != NULL) {
+ _parent->notifyMerge();
+ return;
+ }
+ if (_shouldRetry && _resender->scheduleRetry(*this)) {
+ return;
+ }
+ notifySender();
+}
+
+void
+RoutingNode::addChild(const Route &route)
+{
+ RoutingNode *child = new RoutingNode(*this, route);
+ if (shouldIgnoreResult()) {
+ child->_route.getHop(0).setIgnoreResult(true);
+ }
+ _children.push_back(child);
+}
+
+void
+RoutingNode::setError(uint32_t code, const string &msg)
+{
+ setError(Error(code, msg));
+}
+
+void
+RoutingNode::setError(const Error &err)
+{
+ Reply::UP reply(new EmptyReply());
+ reply->getTrace().setLevel(_trace.getLevel());
+ reply->addError(err);
+ setReply(std::move(reply));
+}
+
+void
+RoutingNode::addError(uint32_t code, const string &msg)
+{
+ addError(Error(code, msg));
+}
+
+void
+RoutingNode::addError(const Error &err)
+{
+ if (_reply.get() != NULL) {
+ _reply->getTrace().swap(_trace);
+ _reply->addError(err);
+ _reply->getTrace().swap(_trace);
+ } else {
+ setError(err);
+ }
+}
+
+void
+RoutingNode::setReply(Reply::UP reply)
+{
+ if (reply.get() != NULL) {
+ _shouldRetry = _resender != NULL && _resender->shouldRetry(*reply);
+ _trace.getRoot().addChild(reply->getTrace().getRoot());
+ reply->getTrace().clear();
+ }
+ _reply = std::move(reply);
+}
+
+void
+RoutingNode::handleReply(Reply::UP reply)
+{
+ setReply(std::move(reply));
+ notifyParent();
+}
+
+void
+RoutingNode::notifyAbort(const string &msg)
+{
+ std::stack<RoutingNode*> mystack;
+ mystack.push(this);
+ while (!mystack.empty()) {
+ RoutingNode *node = mystack.top();
+ mystack.pop();
+ if (!node->_isActive) {
+ // reply not pending
+ } else if (node->_reply.get() != NULL) {
+ node->notifyParent();
+ } else if (node->_children.empty()) {
+ node->setError(ErrorCode::SEND_ABORTED, msg);
+ node->notifyParent();
+ } else {
+ for (std::vector<RoutingNode*>::iterator it = node->_children.begin();
+ it != node->_children.end(); ++it)
+ {
+ mystack.push(*it);
+ }
+ }
+ }
+}
+
+void
+RoutingNode::notifyTransmit()
+{
+ std::vector<RoutingNode*> sendTo;
+ std::stack<RoutingNode*> mystack;
+ mystack.push(this);
+ while (!mystack.empty()) {
+ RoutingNode *node = mystack.top();
+ mystack.pop();
+ if (node->_isActive) {
+ if (node->_children.empty()) {
+ if (node->hasReply()) {
+ node->notifyParent();
+ } else {
+ LOG_ASSERT(node->_serviceAddress.get() != NULL);
+ sendTo.push_back(node);
+ }
+ } else {
+ for (std::vector<RoutingNode*>::iterator it = node->_children.begin();
+ it != node->_children.end(); ++it)
+ {
+ mystack.push(*it);
+ }
+ }
+ }
+ }
+ if (!sendTo.empty()) {
+ _net.send(_msg, sendTo);
+ }
+}
+
+void
+RoutingNode::notifySender()
+{
+ _reply->getTrace().swap(_trace);
+ _replyHandler->handleReply(std::move(_reply));
+}
+
+void
+RoutingNode::notifyMerge()
+{
+ if (vespalib::Atomic::postDec(&_pending) > 1) {
+ return;
+ }
+
+ // Merges the trace information from all children into this. This method takes care not to spend cycles
+ // manipulating the trace in case tracing is disabled.
+ if (_trace.getLevel() > 0) {
+ TraceNode tail;
+ for (std::vector<RoutingNode*>::iterator it = _children.begin();
+ it != _children.end(); ++it)
+ {
+ TraceNode &root = (*it)->_trace.getRoot();
+ tail.addChild(root);
+ root.clear();
+ }
+ tail.setStrict(false);
+ _trace.getRoot().addChild(tail);
+ }
+
+ // Execute the {@link RoutingPolicy#merge(RoutingContext)} method of the current routing policy. If a
+ // policy fails to produce a reply, this attaches an error reply to this node.
+ const PolicyDirective &dir = _routingContext->getDirective();
+ _trace.trace(TraceLevel::SPLIT_MERGE, vespalib::make_vespa_string(
+ "Routing policy '%s' merging replies.",
+ dir.getName().c_str()));
+ try {
+ _policy->merge(*_routingContext);
+ } catch (const std::exception &e) {
+ setError(ErrorCode::POLICY_ERROR, vespalib::make_vespa_string(
+ "Policy '%s' threw an exception; %s",
+ dir.getName().c_str(), e.what()));
+ }
+ if (_reply.get() == NULL) {
+ setError(ErrorCode::APP_FATAL_ERROR, vespalib::make_vespa_string(
+ "Routing policy '%s' failed to merge replies.",
+ dir.getName().c_str()));
+ }
+
+ // Notifies the parent node.
+ notifyParent();
+}
+
+bool
+RoutingNode::hasUnconsumedErrors()
+{
+ bool hasError = false;
+
+ std::stack<RoutingNode*> mystack;
+ mystack.push(this);
+ while (!mystack.empty()) {
+ RoutingNode *node = mystack.top();
+ mystack.pop();
+ if (node->_reply.get() != NULL) {
+ for (uint32_t i = 0; i < node->_reply->getNumErrors(); ++i) {
+ int errorCode = node->_reply->getError(i).getCode();
+ RoutingNode *it = node;
+ while (it != NULL) {
+ if (it->_routingContext.get() != NULL &&
+ it->_routingContext->isConsumableError(errorCode))
+ {
+ errorCode = ErrorCode::NONE;
+ break;
+ }
+ it = it->_parent;
+ }
+ if (errorCode != ErrorCode::NONE) {
+ _shouldRetry = _resender != NULL && _resender->canRetry(errorCode);
+ if (!_shouldRetry) {
+ return true; // no need to continue
+ }
+ hasError = true;
+ }
+ }
+ } else {
+ for (std::vector<RoutingNode*>::iterator it = node->_children.begin();
+ it != node->_children.end(); ++it)
+ {
+ mystack.push(*it);
+ }
+ }
+ }
+
+ return hasError;
+}
+
+bool
+RoutingNode::resolve(uint32_t depth)
+{
+ if (!_route.hasHops()) {
+ setError(ErrorCode::ILLEGAL_ROUTE, "Route has no hops.");
+ return false;
+ }
+ if (!_children.empty()) {
+ return resolveChildren(depth + 1);
+ }
+ while (lookupHop() || lookupRoute()) {
+ if (++depth > 64) {
+ break;
+ }
+ }
+ if (depth > 64) {
+ setError(ErrorCode::ILLEGAL_ROUTE, "Depth limit exceeded.");
+ return false;
+ }
+ if (findErrorDirective()) {
+ return false;
+ }
+ if (findPolicyDirective()) {
+ if (executePolicySelect()) {
+ return resolveChildren(depth + 1);
+ }
+ return _reply.get() != NULL;
+ }
+ _net.allocServiceAddress(*this);
+ return _serviceAddress.get() != NULL || _reply.get() != NULL;
+}
+
+bool
+RoutingNode::lookupHop()
+{
+ RoutingTable::SP table = _mbus.getRoutingTable(_msg.getProtocol());
+ if (table.get() != NULL) {
+ string name = _route.getHop(0).getServiceName();
+ if (table->hasHop(name)) {
+ const HopBlueprint *hop = table->getHop(name);
+ configureFromBlueprint(*hop);
+ _trace.trace(TraceLevel::SPLIT_MERGE,
+ vespalib::make_vespa_string("Recognized '%s' as %s.",
+ name.c_str(), hop->toString().c_str()));
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+RoutingNode::lookupRoute()
+{
+ RoutingTable::SP table = _mbus.getRoutingTable(_msg.getProtocol());
+ Hop &hop = _route.getHop(0);
+ if (hop.getDirective(0)->getType() == IHopDirective::TYPE_ROUTE) {
+ RouteDirective &dir = static_cast<RouteDirective&>(*hop.getDirective(0));
+ if (table.get() == NULL || !table->hasRoute(dir.getName())) {
+ setError(ErrorCode::ILLEGAL_ROUTE,
+ vespalib::make_vespa_string("Route '%s' does not exist.",
+ dir.getName().c_str()));
+ return false;
+ }
+ insertRoute(*table->getRoute(dir.getName()));
+ _trace.trace(TraceLevel::SPLIT_MERGE,
+ vespalib::make_vespa_string("Route '%s' retrieved by directive; new route is '%s'.",
+ dir.getName().c_str(), _route.toString().c_str()));
+ return true;
+ }
+ if (table.get() != NULL) {
+ string name = hop.getServiceName();
+ if (table->hasRoute(name)) {
+ insertRoute(*table->getRoute(name));
+ _trace.trace(TraceLevel::SPLIT_MERGE,
+ vespalib::make_vespa_string("Recognized '%s' as route '%s'.",
+ name.c_str(), _route.toString().c_str()));
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+RoutingNode::insertRoute(const Route &ins)
+{
+ Route route = ins;
+ if (shouldIgnoreResult()) {
+ route.getHop(0).setIgnoreResult(true);
+ }
+ for (uint32_t i = 1; i < _route.getNumHops(); ++i) {
+ route.addHop(_route.getHop(i));
+ }
+ _route = route;
+}
+
+bool
+RoutingNode::findErrorDirective()
+{
+ Hop &hop = _route.getHop(0);
+ for (uint32_t i = 0; i < hop.getNumDirectives(); ++i) {
+ IHopDirective::SP dir = hop.getDirective(i);
+ if (dir->getType() == IHopDirective::TYPE_ERROR) {
+ setError(ErrorCode::ILLEGAL_ROUTE,
+ static_cast<ErrorDirective&>(*dir).getMessage());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+RoutingNode::findPolicyDirective()
+{
+ Hop &hop = _route.getHop(0);
+ for (uint32_t i = 0; i < hop.getNumDirectives(); ++i) {
+ IHopDirective::SP dir = hop.getDirective(i);
+ if (dir->getType() == IHopDirective::TYPE_POLICY) {
+ _routingContext.reset(new RoutingContext(*this, i));
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+RoutingNode::executePolicySelect()
+{
+ const PolicyDirective &dir = _routingContext->getDirective();
+ _policy = _mbus.getRoutingPolicy(_msg.getProtocol(), dir.getName(), dir.getParam());
+ if (_policy.get() == NULL) {
+ setError(ErrorCode::UNKNOWN_POLICY, vespalib::make_vespa_string(
+ "Protocol '%s' could not create routing policy "
+ "'%s' with parameter '%s'.",
+ _msg.getProtocol().c_str(), dir.getName().c_str(),
+ dir.getParam().c_str()));
+ return false;
+ }
+ _trace.trace(TraceLevel::SPLIT_MERGE, vespalib::make_vespa_string(
+ "Running routing policy '%s'.",
+ dir.getName().c_str()));
+ try {
+ _policy->select(*_routingContext);
+ } catch (const std::exception &e) {
+ setError(ErrorCode::POLICY_ERROR, vespalib::make_vespa_string(
+ "Policy '%s' threw an exception; %s",
+ dir.getName().c_str(), e.what()));
+ return false;
+ }
+ if (_children.empty()) {
+ if (_reply.get() == NULL) {
+ setError(ErrorCode::NO_SERVICES_FOR_ROUTE,
+ vespalib::make_vespa_string(
+ "Policy '%s' selected no recipients for "
+ "route '%s'.",
+ dir.getName().c_str(), _route.toString().c_str()));
+ } else {
+ _trace.trace(TraceLevel::SPLIT_MERGE,
+ vespalib::make_vespa_string(
+ "Policy '%s' assigned a reply to this branch.",
+ dir.getName().c_str()));
+ }
+ return false;
+ }
+ for (std::vector<RoutingNode*>::iterator it = _children.begin();
+ it != _children.end(); ++it)
+ {
+ RoutingNode *child = *it;
+ Hop &hop = child->_route.getHop(0);
+ child->_trace.trace(TraceLevel::SPLIT_MERGE,
+ vespalib::make_vespa_string("Component '%s' selected by policy '%s'.",
+ hop.toString().c_str(), dir.getName().c_str()));
+ }
+ return true;
+}
+
+bool
+RoutingNode::resolveChildren(uint32_t childDepth)
+{
+ int numActiveChildren = 0;
+ bool ret = true;
+ for (std::vector<RoutingNode*>::iterator it = _children.begin();
+ it != _children.end(); ++it)
+ {
+ RoutingNode *child = *it;
+ child->_trace.trace(TraceLevel::SPLIT_MERGE,
+ vespalib::make_vespa_string("Resolving '%s'.", child->_route.toString().c_str()));
+ child->_isActive = (child->_reply.get() == NULL);
+ if (child->_isActive) {
+ ++numActiveChildren;
+ if (!child->resolve(childDepth)) {
+ ret = false;
+ break;
+ }
+ } else {
+ child->_trace.trace(TraceLevel::SPLIT_MERGE, "Already completed.");
+ }
+ }
+ _pending = numActiveChildren;
+ return ret;
+}
+
+void
+RoutingNode::configureFromBlueprint(const HopBlueprint &hop)
+{
+ bool ignoreResult = shouldIgnoreResult();
+ _route.setHop(0, *hop.create());
+ if (ignoreResult) {
+ _route.getHop(0).setIgnoreResult(true);
+ }
+ _recipients.clear();
+ for (uint32_t r = 0; r < hop.getNumRecipients(); ++r) {
+ Route recipient;
+ recipient.addHop(hop.getRecipient(r));
+ for (uint32_t h = 1; h < _route.getNumHops(); ++h) {
+ recipient.addHop(_route.getHop(h));
+ }
+ _recipients.push_back(recipient);
+ }
+}
+
+bool
+RoutingNode::tryIgnoreResult()
+{
+ if (!shouldIgnoreResult()) {
+ return false;
+ }
+ if (_reply.get() == NULL || !_reply->hasErrors()) {
+ return false;
+ }
+ setReply(Reply::UP(new EmptyReply()));
+ _trace.trace(TraceLevel::SPLIT_MERGE, "Ignoring errors in reply.");
+ return true;
+}
+
+bool
+RoutingNode::shouldIgnoreResult()
+{
+ return _route.getNumHops() > 0 && _route.getHop(0).getIgnoreResult();
+}
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routingnode.h b/messagebus/src/vespa/messagebus/routing/routingnode.h
new file mode 100644
index 00000000000..92a4b835ba1
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingnode.h
@@ -0,0 +1,446 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <map>
+#include <vespa/messagebus/idiscardhandler.h>
+#include <vespa/messagebus/ireplyhandler.h>
+#include <vespa/messagebus/message.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/network/inetwork.h>
+#include <vespa/messagebus/network/iserviceaddress.h>
+#include <vespa/messagebus/reply.h>
+#include <string>
+#include <vector>
+#include <vespa/vespalib/util/sync.h>
+#include "iroutingpolicy.h"
+#include "resender.h"
+#include "route.h"
+#include "routingcontext.h"
+#include "routingnodeiterator.h"
+
+namespace mbus {
+
+/**
+ * This class represents a node in the routing tree that is created when a route
+ * is resolved. There will be one node per modification of the route. For every
+ * {@link RoutingPolicy} there will be an instance of this that has its policy
+ * and {@link RoutingContext} member set. A policy is oblivious to this class,
+ * it can only access the context object.
+ */
+class RoutingNode : public IReplyHandler {
+private:
+ MessageBus &_mbus;
+ INetwork &_net;
+ Resender *_resender;
+ RoutingNode *_parent;
+ std::vector<Route> _recipients;
+ std::vector<RoutingNode*> _children;
+ IReplyHandler *_replyHandler;
+ IDiscardHandler *_discardHandler;
+ Trace _trace;
+ volatile uint32_t _pending;
+ Message &_msg;
+ Reply::UP _reply;
+ Route _route;
+ IRoutingPolicy::SP _policy;
+ RoutingContext::UP _routingContext;
+ IServiceAddress::UP _serviceAddress;
+ bool _isActive;
+ bool _shouldRetry;
+
+ /**
+ * Constructs a new instance of this class. This is the child node
+ * constructor, and is the constructor used when building the routing tree.
+ *
+ * @param parent The parent routing node.
+ * @param route The route to assign to this.
+ */
+ RoutingNode(RoutingNode &parent, const Route &route);
+
+ /**
+ * Clears the list of child routing node objects, and frees the memory used
+ * to allocate them.
+ */
+ void clearChildren();
+
+ /**
+ * This method collects all unsent leaf nodes and passes them to {@link
+ * Network#send(com.yahoo.messagebus.Message, java.util.List)}. This is
+ * orthogonal to {@link #notifyAbort(String)} in that it ensures that a
+ * reply will return to sender.
+ */
+ void notifyTransmit();
+
+ /**
+ * This method merges the content of all its children, and invokes itself on
+ * the parent node. If not all children are ready for merg, this method does
+ * nothing. The rationale for this is that the last child to receive a reply
+ * will propagate the merge upwards. Once this method reaches the root node,
+ * the reply is either scheduled for resending or passed to the owning reply
+ * handler.
+ */
+ void notifyMerge();
+
+ /**
+ * Returns whether or not transmitting along this routing tree can possibly
+ * succeed. This evaluates to false if either a) there are no leaf nodes to
+ * send to, or b) some leaf node contains a fatal error that is not masked
+ * by a routing policy above it in the tree. If only transient errors would
+ * reach this, the resend flag is set to true.
+ *
+ * @return True if no error reaches this.
+ */
+ bool hasUnconsumedErrors();
+
+ /**
+ * This method performs the necessary selection logic to resolve the next
+ * step of the current route. There is a hard limit to how deep the routing
+ * tree may resolve to, and if that depth is ever exceeded, this method
+ * returns false. This should only really happen if routing has been
+ * misconfigured.
+ *
+ * @param depth The current depth.
+ * @return False if selection failed.
+ */
+ bool resolve(uint32_t depth);
+
+ /**
+ * This method checks to see whether the string representation of the
+ * current hop is actually the name of another. If a hop is found, the
+ * first hop of the current route is replaced by this.
+ *
+ * @return True if a hop was found and added.
+ */
+ bool lookupHop();
+
+ /**
+ * This method checks to see whether the current hop contains a {@link
+ * RouteDirective}, or if its string representation is actually the name of
+ * a configured route. If a route is found, the first hop of the current
+ * route is replaced by expanding the named route. If a route directive
+ * requests a non-existant route, this method creates an error-reply for
+ * this node.
+ *
+ * @return True if a route was found and added.
+ * @see #insertRoute(Route)
+ */
+ bool lookupRoute();
+
+ /**
+ * This method replaces the first hop of the current route with the given
+ * route.
+ *
+ * @param ins The route to insert.
+ */
+ void insertRoute(const Route &ins);
+
+ /**
+ * This method traverses the current hop looking for an isntance of {@link
+ * ErrorDirective}. If one is found, this method assigns a corresponding
+ * error reply to this node.
+ *
+ * @return True if an error was found.
+ */
+ bool findErrorDirective();
+
+ /**
+ * This method traverses the current hop looking for an instance of {@link
+ * PolicyDirective}. If one is found, this method creates and assigns a
+ * routing context to this.
+ *
+ * @return True if a policy was found.
+ */
+ bool findPolicyDirective();
+
+ /**
+ * Creates the {@link RoutingPolicy} referenced by the current routing
+ * context, and executes its {@link RoutingPolicy#select(RoutingContext)}
+ * method.
+ *
+ * @return True if at least one child was added.
+ */
+ bool executePolicySelect();
+
+ /**
+ * This method invokes the {@link #resolve(int)} method of all the child
+ * nodes of this. If any of these exceed the depth limit, this method
+ * returns false.
+ *
+ * @param childDepth The depth of the children.
+ * @return False if depth limit was exceeded.
+ */
+ bool resolveChildren(uint32_t childDepth);
+
+ /**
+ * Configures this node based on a hop blueprint. For each recipient in the
+ * blueprint it creates a copy of the current route, and sets the first hop
+ * of that route to be the configured recipient hop. In effect, this
+ * replaces the current hop and retains the rest of the route.
+ *
+ * @param hop The blueprint to use for configuration.
+ */
+ void configureFromBlueprint(const HopBlueprint &hop);
+
+ /**
+ * This method mergs this node as ready for merge. If it has a parent
+ * routing node, its pending member is decremented. If this causes the
+ * parent's pending count to reach zero, its {@link #notifyMerge()} method
+ * is invoked. A special flag is used to make sure that failed resending
+ * avoids notifying parents of previously resolved branches of the tree.
+ */
+ void notifyParent();
+
+ /**
+ * If a reply has been set containing an error, and {@link
+ * #shouldIgnoreResult()} returns <tt>true</tt>, this method replaces that
+ * reply with one that has no error.
+ *
+ * @return Whether or not the reply was replaced.
+ */
+ bool tryIgnoreResult();
+
+ /**
+ * Returns whether or not to ignore any errors that may occur on this node
+ * or any of its children.
+ *
+ * @return True to ignore the result.
+ */
+ bool shouldIgnoreResult();
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<RoutingNode> UP;
+
+ /**
+ * Constructs a new instance of this class. This is the root node
+ * constructor, and will be used by the different sessions for sending
+ * messages.
+ *
+ * @param mbus The message bus on which we are running.
+ * @param net The network layer we are to transmit through.
+ * @param resender The resender to schedule with.
+ * @param replyHandler The handler to receive the final reply.
+ * @param msg The message being sent.
+ * @param discardHandler The handler to notify when discarding this.
+ */
+ RoutingNode(MessageBus &mbus, INetwork &net, Resender *resender,
+ IReplyHandler &replyHandler, Message &msg,
+ IDiscardHandler *discardHandler = NULL);
+
+ /**
+ * Destructor. Frees up any allocated resources, namely all child nodes of
+ * this.
+ */
+ ~RoutingNode();
+
+ /**
+ * Discards this routing node. Invoking this will notify the parent {@link
+ * SendProxy} to ensure that the corresponding message is discarded, and all
+ * allocated memory is freed. This is a required step to ensure safe
+ * shutdown if you need to destroy a message bus instance while there are
+ * still routing nodes alive in your application.
+ */
+ void discard();
+
+ /**
+ * This is the single entry-point for sending a message along a route. This
+ * can only be invoked on the root node of a routing tree. It runs all the
+ * necessary selection, verification and transmission logic. Once this has
+ * been called, it guarantees that a reply is returned to the registered
+ * reply handler.
+ */
+ void send();
+
+ /**
+ * This method is used to reset the internal state of routing nodes that
+ * will be resent. If a routing policy sets {@link
+ * RoutingContext#setSelectOnResend(boolean)} to true, this method will
+ * reroute everything from that node onwards. If that flag is not set,
+ * scheduling recurses into any child that got a reply with only transient
+ * errors. Finally, if neither this node or none of its children were
+ * scheduled for resending, force reroute from this.
+ */
+ void prepareForRetry();
+
+ /**
+ * This method may only be invoked on a root node, as it passes the current
+ * reply to the member {@link ReplyHandler}. The actual call to the handler
+ * is done in a separate thread to avoid deadlocks.
+ */
+ void notifySender();
+
+ /**
+ * This method assigns an error reply to all unsent leaf nodes, and invokes
+ * {@link #notifyParent()} on them. This has the effect of ensuring that a
+ * reply will return to sender.
+ *
+ * @param msg The error message to assign.
+ */
+ void notifyAbort(const string &msg);
+
+ /**
+ * Adds a child routing node to this based on a route. This is package
+ * private because client code should only access it through a {@link
+ * RoutingPolicy} and its {@link RoutingContext#addChild(Route)} method.
+ *
+ * @param route The route to store in the child node.
+ */
+ void addChild(const Route &route);
+
+ /**
+ * This is a convenience method to call {@link #setError(Error)}.
+ *
+ * @param code The code of the error to set.
+ * @param msg The message of the error to set.
+ */
+ void setError(uint32_t code, const string &msg);
+
+ /**
+ * This is a convenience method to assign an {@link EmptyReply} containing a
+ * single error to this. This also fiddles with the trace object so that the
+ * error gets written to it.
+ *
+ * @param err The error to set.
+ * @see #setReply(Reply)
+ */
+ void setError(const Error &err);
+
+ /**
+ * This is a convenience method to call {@link #addError(Error)}.
+ *
+ * @param code The code of the error to add.
+ * @param msg The message of the error to add.
+ */
+ void addError(uint32_t code, const string &msg);
+
+ /**
+ * This is a convenience method to add an error to this. If a reply has
+ * already been set, this method will add the error to it. If no reply is
+ * set, this method calls {@link #setError(Error)}. This method also fiddles
+ * with the trace object so that the error gets written to it.
+ *
+ * @param err The error to add.
+ */
+ void addError(const Error &err);
+
+ /**
+ * Returns the message bus being used to send the message.
+ *
+ * @return The message bus.
+ */
+ MessageBus &getMessageBus() { return _mbus; }
+
+ /**
+ * Returns the network being used to send the message.
+ *
+ * @return The network layer.
+ */
+ INetwork &getNetwork() { return _net; }
+
+ /**
+ * Returns the message being routed. You should NEVER modify a message that
+ * is retrieved from a routing node or context, as the result of doing so is
+ * undefined.
+ *
+ * @return The message being routed.
+ */
+ Message &getMessage() { return _msg; }
+
+ /**
+ * Returns the trace object for this node. Each node has a separate trace
+ * object so that merging can be done correctly.
+ *
+ * @return The trace object.
+ */
+ Trace &getTrace() { return _trace; }
+
+ /**
+ * Returns the route object as it exists at this point of the tree.
+ *
+ * @return The route at this point.
+ */
+ const Route &getRoute() const { return _route; }
+
+ /**
+ * Returns whether or not this node contains a reply.
+ *
+ * @return True if this node has a reply.
+ */
+ bool hasReply() const { return _reply.get() != NULL; }
+
+ /**
+ * Returns the reply of this node.
+ *
+ * @return The reply assigned to this node.
+ */
+ Reply::UP getReply() { return std::move(_reply); }
+
+ /**
+ * Returns a reference to the reply of this node. This should never be
+ * called unless {@link #hasReply()} is true, because you will be deref'ing
+ * null.
+ *
+ * @return The reply assigned to this node.
+ */
+ Reply &getReplyRef() { return *_reply; }
+
+ /**
+ * Sets the reply of this routing node. This method also updates the
+ * internal state of this node; it is tagged for resending if the reply has
+ * only transient errors, and the reply's {@link Trace} is copied. This
+ * method <u>does not</u> call the parent node's {@link #notifyMerge()}.
+ *
+ * @param reply The reply to set.
+ */
+ void setReply(Reply::UP reply);
+
+ /**
+ * Returns the list of configured recipient {@link Route routes}. This is
+ * accessed by client code through a more strict api in {@link
+ * RoutingContext}.
+ *
+ * @return The list of recipients.
+ */
+ std::vector<Route> &getRecipients() { return _recipients; }
+
+ /**
+ * Returns the list of current child nodes. This is accessed by client code
+ * through a more strict api in {@link RoutingContext}.
+ *
+ * @return The list of children.
+ */
+ std::vector<RoutingNode*> &getChildren() { return _children; }
+
+ /**
+ * Returns whether or not the service address of this node has been set.
+ *
+ * @return True if an address is set.
+ */
+ bool hasServiceAddress() { return _serviceAddress.get() != NULL; }
+
+ /**
+ * Returns the service address of this node. This is attached by the network
+ * layer, and should only ever be present in leaf nodes. Do not invoke this
+ * unless {@link #hasServiceAddress()} is true, or you will deref null.
+ *
+ * @return The recipient address.
+ */
+ IServiceAddress &getServiceAddress() { return *_serviceAddress; }
+
+ /**
+ * Sets the service address of this node. This is called by the network
+ * layer as this calls its {@link Network#resolveRecipient(RoutingNode)}
+ * method.
+ *
+ * @param serviceAddress The recipient address.
+ */
+ void setServiceAddress(IServiceAddress::UP serviceAddress) { _serviceAddress = std::move(serviceAddress); }
+
+ // Inherit doc from IReplyHandler.
+ virtual void handleReply(Reply::UP reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routingnodeiterator.cpp b/messagebus/src/vespa/messagebus/routing/routingnodeiterator.cpp
new file mode 100644
index 00000000000..9528a896736
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingnodeiterator.cpp
@@ -0,0 +1,64 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include "routingnode.h"
+
+namespace mbus {
+
+RoutingNodeIterator::RoutingNodeIterator(std::vector<RoutingNode*> &children) :
+ _pos(children.begin()),
+ _end(children.end())
+{
+ // empty
+}
+
+bool
+RoutingNodeIterator::isValid()
+{
+ return _pos != _end;
+}
+
+RoutingNodeIterator &
+RoutingNodeIterator::next()
+{
+ ++_pos;
+ return *this;
+}
+
+RoutingNodeIterator &
+RoutingNodeIterator::skip(uint32_t num)
+{
+ for (uint32_t i = 0; i < num && isValid(); ++i) {
+ next();
+ }
+ return *this;
+}
+
+const Route &
+RoutingNodeIterator::getRoute() const
+{
+ return (*_pos)->getRoute();
+}
+
+bool
+RoutingNodeIterator::hasReply() const
+{
+ return (*_pos)->hasReply();
+}
+
+Reply::UP
+RoutingNodeIterator::removeReply()
+{
+ RoutingNode *node = *_pos;
+ Reply::UP ret = node->getReply();
+ ret->getTrace().setLevel(node->getTrace().getLevel());
+ ret->getTrace().swap(node->getTrace());
+ return ret;
+}
+
+const Reply &
+RoutingNodeIterator::getReplyRef() const
+{
+ return (*_pos)->getReplyRef();
+}
+
+} // mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routingnodeiterator.h b/messagebus/src/vespa/messagebus/routing/routingnodeiterator.h
new file mode 100644
index 00000000000..12ab0f63485
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingnodeiterator.h
@@ -0,0 +1,80 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/messagebus/reply.h>
+#include "route.h"
+
+namespace mbus {
+
+class RoutingNode;
+
+/**
+ * Implements an iterator for the child routing contexts of this. Use {@link
+ * RoutingContext#getChildIterator()} to retrieve an instance of this.
+ */
+class RoutingNodeIterator {
+private:
+ std::vector<RoutingNode*>::iterator _pos, _end;
+
+public:
+ /**
+ * Constructs a new iterator based on a given list.
+ *
+ * @param children The list to iterate through.
+ */
+ RoutingNodeIterator(std::vector<RoutingNode*> &children);
+
+ /**
+ * Returns whether or not this iterator is valid.
+ *
+ * @return True if we are still pointing to a valid entry.
+ */
+ bool isValid();
+
+ /**
+ * Steps to the next child in the map.
+ *
+ * @return This, to allow chaining.
+ */
+ RoutingNodeIterator &next();
+
+ /**
+ * Skips the given number of children.
+ *
+ * @param num The number of children to skip.
+ * @return This, to allow chaining.
+ */
+ RoutingNodeIterator &skip(uint32_t num);
+
+ /**
+ * Returns the route of the current child.
+ *
+ * @return The route.
+ */
+ const Route &getRoute() const;
+
+ /**
+ * Returns whether or not a reply is set in the current child.
+ *
+ * @return True if a reply is available.
+ */
+ bool hasReply() const;
+
+ /**
+ * Removes and returns the reply of the current child. This is the correct way of reusing a reply of a
+ * child node, the {@link #getReplyRef()} should be used when just inspecting a child reply.
+ *
+ * @return The reply.
+ */
+ Reply::UP removeReply();
+
+ /**
+ * Returns the reply of the current child.
+ *
+ * @return The reply.
+ */
+ const Reply &getReplyRef() const;
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routingspec.cpp b/messagebus/src/vespa/messagebus/routing/routingspec.cpp
new file mode 100644
index 00000000000..7f4f605d3ff
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingspec.cpp
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routingspec");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "routingspec.h"
+
+namespace mbus {
+
+RoutingSpec::RoutingSpec() :
+ _tables()
+{
+ // empty
+}
+
+RoutingTableSpec
+RoutingSpec::removeTable(uint32_t i)
+{
+ RoutingTableSpec ret = _tables[i];
+ _tables.erase(_tables.begin() + i);
+ return ret;
+}
+
+string
+RoutingSpec::toConfigString(const string &input)
+{
+ string ret;
+ ret.append("\"");
+ for (uint32_t i = 0, len = input.size(); i < len; ++i) {
+ if (input[i] == '\\') {
+ ret.append("\\\\");
+ } else if (input[i] == '"') {
+ ret.append("\\\"");
+ } else if (input[i] == '\n') {
+ ret.append("\\n");
+ } else if (input[i] == 0) {
+ ret.append("\\x00");
+ } else {
+ ret += input[i];
+ }
+ }
+ ret.append("\"");
+ return ret;
+}
+
+void
+RoutingSpec::toConfig(string &cfg, const string &prefix) const
+{
+ uint32_t numTables = _tables.size();
+ if (numTables > 0) {
+ cfg.append(prefix).append("routingtable[").append(vespalib::make_vespa_string("%d", numTables)).append("]\n");
+ for (uint32_t i = 0; i < numTables; ++i) {
+ _tables[i].toConfig(cfg, vespalib::make_vespa_string("%sroutingtable[%d].", prefix.c_str(), i));
+ }
+ }
+}
+
+string
+RoutingSpec::toString() const
+{
+ string ret = "";
+ toConfig(ret, "");
+ return ret;
+}
+
+bool
+RoutingSpec::operator==(const RoutingSpec &rhs) const
+{
+ if (_tables.size() != rhs._tables.size()) {
+ return false;
+ }
+ for (uint32_t i = 0, len = _tables.size(); i < len; ++i) {
+ if (_tables[i] != rhs._tables[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routingspec.h b/messagebus/src/vespa/messagebus/routing/routingspec.h
new file mode 100644
index 00000000000..381c0531406
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingspec.h
@@ -0,0 +1,136 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vector>
+#include "routingtablespec.h"
+
+namespace mbus {
+
+/**
+ * Along with the {@link RoutingTableSpec}, {@link RouteSpec} and {@link HopSpec}, this holds the routing specifications
+ * for all protocols. The only way a client can configure or alter the settings of a message bus instance is through
+ * these classes.
+ *
+ * This class is the root spec class for configuring message bus routing.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class RoutingSpec {
+private:
+ std::vector<RoutingTableSpec> _tables;
+
+public:
+ /**
+ * Creates an empty specification.
+ */
+ RoutingSpec();
+
+ /**
+ * Returns whether or not there are routing table specs contained in this.
+ *
+ * @return True if there is at least one table.
+ */
+ bool hasTables() const { return !_tables.empty(); }
+
+ /**
+ * Returns the number of routing table specs that are contained in this.
+ *
+ * @return The number of routing tables.
+ */
+ uint32_t getNumTables() const { return _tables.size(); }
+
+ /**
+ * Returns the routing table spec at the given index.
+ *
+ * @param i The index of the routing table to return.
+ * @return The routing table at the given index.
+ */
+ RoutingTableSpec &getTable(uint32_t i) { return _tables[i]; }
+
+ /**
+ * Returns a const reference to the routing table spec at the given index.
+ *
+ * @param i The index of the routing table to return.
+ * @return The routing table at the given index.
+ */
+ const RoutingTableSpec &getTable(uint32_t i) const { return _tables[i]; }
+
+ /**
+ * Sets the routing table spec at the given index.
+ *
+ * @param i The index at which to set the routing table.
+ * @param table The routing table to set.
+ * @return This, to allow chaining.
+ */
+ RoutingSpec &setTable(uint32_t i, const RoutingTableSpec &table) { _tables[i] = table; return *this; }
+
+ /**
+ * Adds a routing table spec to the list of tables.
+ *
+ * @param table The routing table to add.
+ * @return This, to allow chaining.
+ */
+ RoutingSpec &addTable(const RoutingTableSpec &table) { _tables.push_back(table); return *this; }
+
+ /**
+ * Returns the routing table spec at the given index.
+ *
+ * @param i The index of the routing table to remove.
+ * @return The removed routing table.
+ */
+ RoutingTableSpec removeTable(uint32_t i);
+
+ /**
+ * Clears the list of routing table specs contained in this.
+ *
+ * @return This, to allow chaining.
+ */
+ RoutingSpec &clearTables() { _tables.clear(); return *this; }
+
+ /**
+ * Appends the content of this to the given config string.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ void toConfig(string &cfg, const string &prefix) const;
+
+ /**
+ * Convert a string value to a quoted value suitable for use in a config string.
+ * <p/>
+ * Adds double quotes before and after, and adds backslash-escapes to any double quotes that was contained in the
+ * string. A null pointer will produce the special unquoted string null that the config library will convert back
+ * to a null pointer.
+ *
+ * @param input the String to be escaped
+ * @return an escaped String
+ */
+ static string toConfigString(const string &input);
+
+ /**
+ * Returns a string representation of this.
+ *
+ * @return The string.
+ */
+ string toString() const;
+
+ /**
+ * Implements the equality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this equals the other.
+ */
+ bool operator==(const RoutingSpec &rhs) const;
+
+ /**
+ * Implements the inequality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this does not equals the other.
+ */
+ bool operator!=(const RoutingSpec &rhs) const { return !(*this == rhs); }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routingtable.cpp b/messagebus/src/vespa/messagebus/routing/routingtable.cpp
new file mode 100644
index 00000000000..fa58570d2a5
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingtable.cpp
@@ -0,0 +1,77 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routingtable");
+#include <vespa/messagebus/iprotocol.h>
+#include <vespa/messagebus/message.h>
+#include <vector>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "hop.h"
+#include "routingtable.h"
+#include "routingtablespec.h"
+
+namespace mbus {
+
+RoutingTable::HopIterator::HopIterator(const std::map<string, HopBlueprint> &hops) :
+ _pos(hops.begin()),
+ _end(hops.end())
+{
+ // empty
+}
+
+RoutingTable::RouteIterator::RouteIterator(const std::map<string, Route> &routes) :
+ _pos(routes.begin()),
+ _end(routes.end())
+{
+ // empty
+}
+
+RoutingTable::RoutingTable(const RoutingTableSpec &spec) :
+ _name(spec.getProtocol()),
+ _hops(),
+ _routes()
+{
+ for (uint32_t i = 0; i < spec.getNumHops(); ++i) {
+ const HopSpec& hopSpec = spec.getHop(i);
+ _hops.insert(std::map<string, HopBlueprint>::value_type(hopSpec.getName(),
+ HopBlueprint(hopSpec)));
+ }
+ for (uint32_t i = 0; i < spec.getNumRoutes(); ++i) {
+ Route route;
+ const RouteSpec &routeSpec = spec.getRoute(i);
+ for (uint32_t j = 0; j < routeSpec.getNumHops(); ++j) {
+ route.addHop(Hop(routeSpec.getHop(j)));
+ }
+ _routes.insert(std::map<string, Route>::value_type(routeSpec.getName(),
+ route));
+ }
+}
+
+bool
+RoutingTable::hasHop(const string &name) const
+{
+ return _hops.find(name) != _hops.end();
+}
+
+const HopBlueprint *
+RoutingTable::getHop(const string &name) const
+{
+ std::map<string, HopBlueprint>::const_iterator it = _hops.find(name);
+ return it != _hops.end() ? &(it->second) : NULL;
+}
+
+bool
+RoutingTable::hasRoute(const string &name) const
+{
+ return _routes.find(name) != _routes.end();
+}
+
+const Route *
+RoutingTable::getRoute(const string &name) const
+{
+ std::map<string, Route>::const_iterator it = _routes.find(name);
+ return it != _routes.end() ? &(it->second) : NULL;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routingtable.h b/messagebus/src/vespa/messagebus/routing/routingtable.h
new file mode 100644
index 00000000000..84b371ea229
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingtable.h
@@ -0,0 +1,152 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <map>
+#include <string>
+#include "hopblueprint.h"
+#include "route.h"
+
+namespace mbus {
+
+class RoutingTableSpec;
+class IProtocol;
+class INetwork;
+class IReplyHandler;
+class Message;
+
+/**
+ * At any time there may only ever be zero or one routing table registered in message bus for each protocol. This class
+ * contains a list of named hops and routes that may be used to substitute references to these during route resolving.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class RoutingTable : public boost::noncopyable {
+private:
+ string _name;
+ std::map<string, HopBlueprint> _hops;
+ std::map<string, Route> _routes;
+
+public:
+ /**
+ * Implements an iterator for the hops contained in this table.
+ */
+ class HopIterator {
+ private:
+ std::map<string, HopBlueprint>::const_iterator _pos, _end;
+
+ public:
+ HopIterator(const std::map<string, HopBlueprint> &hops);
+
+ bool isValid() { return _pos != _end; }
+ void next() { ++_pos; }
+ const string &getName() { return _pos->first; }
+ const HopBlueprint &getHop() { return _pos->second; }
+ };
+
+ /**
+ * Implements an iterator for the routes contained in this table.
+ */
+ class RouteIterator {
+ private:
+ std::map<string, Route>::const_iterator _pos, _end;
+
+ public:
+ RouteIterator(const std::map<string, Route> &hops);
+
+ bool isValid() { return _pos != _end; }
+ void next() { ++_pos; }
+ const string &getName() { return _pos->first; }
+ const Route &getRoute() { return _pos->second; }
+ };
+
+ /**
+ * Convenience typedef for a shared pointer to a RoutingTable object.
+ */
+ typedef std::shared_ptr<RoutingTable> SP;
+
+ /**
+ * Creates a new routing table based on a given specification. This also verifies the integrity of the table.
+ *
+ * @param spec The specification to use.
+ */
+ RoutingTable(const RoutingTableSpec &spec);
+
+ /**
+ * Returns whether or not there are any hops in this routing table.
+ *
+ * @return True if there is at least one hop.
+ */
+ bool hasHops() const { return !_hops.empty(); }
+
+ /**
+ * Returns the number of hops that are contained in this.
+ *
+ * @return The number of hops.
+ */
+ uint32_t getNumHops() const { return _hops.size(); }
+
+ /**
+ * Returns whether or not there exists a named hop in this.
+ *
+ * @param name The name of the hop to look for.
+ * @return True if the named hop exists.
+ */
+ bool hasHop(const string &name) const;
+
+ /**
+ * Returns the named hop, may be null.
+ *
+ * @param name The name of the hop to return.
+ * @return The hop implementation object.
+ */
+ const HopBlueprint *getHop(const string &name) const;
+
+ /**
+ * Returns an iterator for the hops of this.
+ *
+ * @return An iterator.
+ */
+ HopIterator getHopIterator() const { return HopIterator(_hops); }
+
+ /**
+ * Returns whether or not there are any routes in this routing table.
+ *
+ * @return True if there is at least one route.
+ */
+ bool hasRoutes() const { return !_routes.empty(); }
+
+ /**
+ * Returns the number of routes that are contained in this.
+ *
+ * @return The number of routes.
+ */
+ uint32_t getNumRoutes() const { return _routes.size(); }
+
+ /**
+ * Returns whether or not there exists a named route in this.
+ *
+ * @param name The name of the route to look for.
+ * @return True if the named route exists.
+ */
+ bool hasRoute(const string &name) const;
+
+ /**
+ * Returns the named route, may be null.
+ *
+ * @param name The name of the route to return.
+ * @return The route implementation object.
+ */
+ const Route *getRoute(const string &name) const;
+
+ /**
+ * Returns an iterator for the routes of this.
+ *
+ * @return An iterator.
+ */
+ RouteIterator getRouteIterator() const { return RouteIterator(_routes); }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/routingtablespec.cpp b/messagebus/src/vespa/messagebus/routing/routingtablespec.cpp
new file mode 100644
index 00000000000..9b3ec6634e2
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingtablespec.cpp
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".routingtablespec");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "routingspec.h"
+
+namespace mbus {
+
+RoutingTableSpec::RoutingTableSpec(const string &protocol) :
+ _protocol(protocol),
+ _hops(),
+ _routes()
+{
+ // empty
+}
+
+HopSpec
+RoutingTableSpec::removeHop(uint32_t i)
+{
+ HopSpec ret = _hops[i];
+ _hops.erase(_hops.begin() + i);
+ return ret;
+}
+
+RouteSpec
+RoutingTableSpec::removeRoute(uint32_t i)
+{
+ RouteSpec ret = _routes[i];
+ _routes.erase(_routes.begin() + i);
+ return ret;
+}
+
+void
+RoutingTableSpec::toConfig(string &cfg, const string &prefix) const
+{
+ cfg.append(prefix).append("protocol ").append(RoutingSpec::toConfigString(_protocol)).append("\n");
+ uint32_t numHops = _hops.size();
+ if (numHops > 0) {
+ cfg.append(prefix).append("hop[").append(vespalib::make_vespa_string("%d", numHops)).append("]\n");
+ for (uint32_t i = 0; i < numHops; ++i) {
+ _hops[i].toConfig(cfg, vespalib::make_vespa_string("%shop[%d].", prefix.c_str(), i));
+ }
+ }
+ uint32_t numRoutes = _routes.size();
+ if (numRoutes > 0) {
+ cfg.append(prefix).append("route[").append(vespalib::make_vespa_string("%d", numRoutes)).append("]\n");
+ for (uint32_t i = 0; i < numRoutes; ++i) {
+ _routes[i].toConfig(cfg, vespalib::make_vespa_string("%sroute[%d].", prefix.c_str(), i));
+ }
+ }
+}
+
+string
+RoutingTableSpec::toString() const
+{
+ string ret = "";
+ toConfig(ret, "");
+ return ret;
+}
+
+bool
+RoutingTableSpec::operator==(const RoutingTableSpec &rhs) const
+{
+ if (_protocol != rhs._protocol) {
+ return false;
+ }
+ if (_hops.size() != rhs._hops.size()) {
+ return false;
+ }
+ for (uint32_t i = 0, len = _hops.size(); i < len; ++i) {
+ if (_hops[i] != rhs._hops[i]) {
+ return false;
+ }
+ }
+ if (_routes.size() != rhs._routes.size()) {
+ return false;
+ }
+ for (uint32_t i = 0, len = _routes.size(); i < len; ++i) {
+ if (_routes[i] != rhs._routes[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/routing/routingtablespec.h b/messagebus/src/vespa/messagebus/routing/routingtablespec.h
new file mode 100644
index 00000000000..993031a0adf
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/routingtablespec.h
@@ -0,0 +1,192 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <string>
+#include <vector>
+#include "hopspec.h"
+#include "routespec.h"
+
+namespace mbus {
+
+/**
+ * Along with the {@link RoutingSpec}, {@link RouteSpec} and {@link HopSpec}, this holds the routing specifications for
+ * all protocols. The only way a client can configure or alter the settings of a message bus instance is through these
+ * classes.
+ *
+ * This class contains the spec for a single routing table, which corresponds to exactly one protocol.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class RoutingTableSpec {
+private:
+ string _protocol;
+ std::vector<HopSpec> _hops;
+ std::vector<RouteSpec> _routes;
+
+public:
+ /**
+ * Creates a new routing table specification for a named protocol.
+ *
+ * @param protocol The name of the protocol that this belongs to.
+ */
+ RoutingTableSpec(const string &protocol);
+
+ /**
+ * Returns the name of the protocol that this is the routing table for.
+ *
+ * @return The protocol name.
+ */
+ const string &getProtocol() const { return _protocol; }
+
+ /**
+ * Returns whether or not there are any hop specs contained in this.
+ *
+ * @return True if there is at least one hop.
+ */
+ bool hasHops() const { return !_hops.empty(); }
+
+ /**
+ * Returns the number of hops that are contained in this table.
+ *
+ * @return The number of hops.
+ */
+ uint32_t getNumHops() const { return _hops.size(); }
+
+ /**
+ * Returns the hop spec at the given index.
+ *
+ * @param i The index of the hop to return.
+ * @return The hop at the given position.
+ */
+ HopSpec &getHop(uint32_t i) { return _hops[i]; }
+
+ /**
+ * Returns a const reference to the hop spec at the given index.
+ *
+ * @param i The index of the hop to return.
+ * @return The hop at the given position.
+ */
+ const HopSpec &getHop(uint32_t i) const { return _hops[i]; }
+
+ /**
+ * Adds the given hop spec to this.
+ *
+ * @param hop The hop to add.
+ * @return This, to allow chaining.
+ */
+ RoutingTableSpec &addHop(const HopSpec &hop) { _hops.push_back(hop); return *this; }
+
+ /**
+ * Sets the hop spec at the given index.
+ *
+ * @param i The index at which to set the hop.
+ * @param hop The hop to set.
+ * @return This, to allow chaining.
+ */
+ RoutingTableSpec &setHop(uint32_t i, const HopSpec &hop) { _hops[i] = hop; return *this; }
+
+ /**
+ * Removes the hop spec at the given index.
+ *
+ * @param i The index of the hop to remove.
+ * @return The removed hop.
+ */
+ HopSpec removeHop(uint32_t i);
+
+ /**
+ * Clears the list of hop specs contained in this.
+ *
+ * @return This, to allow chaining.
+ */
+ RoutingTableSpec &clearHops() { _hops.clear(); return *this; }
+
+ /**
+ * Returns whether or not there are any route specs contained in this.
+ *
+ * @return True if there is at least one route.
+ */
+ bool hasRoutes() const { return !_routes.empty(); }
+
+ /**
+ * Returns the number of route specs contained in this.
+ *
+ * @return The number of routes.
+ */
+ uint32_t getNumRoutes() const { return _routes.size(); }
+
+ /**
+ * Returns the route spec at the given index.
+ *
+ * @param i The index of the route to return.
+ * @return The route at the given index.
+ */
+ RouteSpec &getRoute(uint32_t i) { return _routes[i]; }
+
+ /**
+ * Returns a const reference to the route spec at the given index.
+ *
+ * @param i The index of the route to return.
+ * @return The route at the given index.
+ */
+ const RouteSpec &getRoute(uint32_t i) const { return _routes[i]; }
+
+ /**
+ * Adds a route spec to this.
+ *
+ * @param route The route to add.
+ * @return This, to allow chaining.
+ */
+ RoutingTableSpec &addRoute(const RouteSpec &route) { _routes.push_back(route); return *this; }
+
+ /**
+ * Sets the route spec at the given index.
+ *
+ * @param i The index at which to set the route.
+ * @param route The route to set.
+ * @return This, to allow chaining.
+ */
+ RoutingTableSpec &setRoute(uint32_t i, const RouteSpec &route) { _routes[i] = route; return *this; }
+
+ /**
+ * Removes a route spec at a given index.
+ *
+ * @param i The index of the route to remove.
+ * @return The removed route.
+ */
+ RouteSpec removeRoute(uint32_t i);
+
+ /**
+ * Appends the content of this to the given config string.
+ *
+ * @param cfg The config to add to.
+ * @param prefix The prefix to use for each add.
+ */
+ void toConfig(string &cfg, const string &prefix) const;
+
+ /**
+ * Returns a string representation of this.
+ *
+ * @return The string.
+ */
+ string toString() const;
+
+ /**
+ * Implements the equality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this equals the other.
+ */
+ bool operator==(const RoutingTableSpec &rhs) const;
+
+ /**
+ * Implements the inequality operator.
+ *
+ * @param rhs The object to compare to.
+ * @return True if this does not equals the other.
+ */
+ bool operator!=(const RoutingTableSpec &rhs) const { return !(*this == rhs); }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/tcpdirective.cpp b/messagebus/src/vespa/messagebus/routing/tcpdirective.cpp
new file mode 100644
index 00000000000..041f089c472
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/tcpdirective.cpp
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include "tcpdirective.h"
+
+namespace mbus {
+
+TcpDirective::TcpDirective(const vespalib::stringref &host, uint32_t port, const vespalib::stringref &session) :
+ _host(host),
+ _port(port),
+ _session(session)
+{
+ // empty
+}
+
+bool
+TcpDirective::matches(const IHopDirective &dir) const
+{
+ if (dir.getType() != TYPE_TCP) {
+ return false;
+ }
+ const TcpDirective &rhs = static_cast<const TcpDirective&>(dir);
+ return _host == rhs._host && _port == rhs._port && _session == rhs._session;
+}
+
+string
+TcpDirective::toString() const
+{
+ vespalib::asciistream os;
+ os << "tcp/" << _host << ':' << _port << '/' << _session;
+ return os.str();
+}
+
+string
+TcpDirective::toDebugString() const
+{
+ vespalib::asciistream os;
+ os << "TcpDirective(host = '" << _host << "', port = " << _port << ", session = '" << _session << "')";
+ return os.str();
+}
+
+} // mbus
diff --git a/messagebus/src/vespa/messagebus/routing/tcpdirective.h b/messagebus/src/vespa/messagebus/routing/tcpdirective.h
new file mode 100644
index 00000000000..ab8c422c387
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/tcpdirective.h
@@ -0,0 +1,60 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <stdint.h>
+#include "ihopdirective.h"
+
+namespace mbus {
+
+/**
+ * This class represents a tcp directive within a {@link Hop}'s selector. This is a connection string used to establish
+ * a direct connection to a host, bypassing service lookups through Slobrok.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class TcpDirective : public IHopDirective {
+private:
+ string _host;
+ uint32_t _port;
+ string _session;
+
+public:
+ /**
+ * Constructs a new directive to route directly to a tcp address.
+ *
+ * @param host The host name to connect to.
+ * @param port The port to connect to.
+ * @param session The session to route to.
+ */
+ TcpDirective(const vespalib::stringref &host, uint32_t port, const vespalib::stringref &session);
+
+ /**
+ * Returns the host to connect to. This may be an ip address or a name.
+ *
+ * @return The host.
+ */
+ const string &getHost() const { return _host; }
+
+ /**
+ * Returns the port to connect to on the remove host.
+ *
+ * @return The port number.
+ */
+ uint32_t getPort() const { return _port; }
+
+ /**
+ * Returns the name of the session to route to.
+ *
+ * @return The session name.
+ */
+ const string &getSession() const { return _session; }
+
+ virtual Type getType() const { return TYPE_TCP; }
+ virtual bool matches(const IHopDirective &dir) const;
+ virtual string toString() const;
+ virtual string toDebugString() const;
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/routing/verbatimdirective.cpp b/messagebus/src/vespa/messagebus/routing/verbatimdirective.cpp
new file mode 100644
index 00000000000..0fc91209f3c
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/verbatimdirective.cpp
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "verbatimdirective.h"
+
+namespace mbus {
+
+VerbatimDirective::VerbatimDirective(const vespalib::stringref &image) :
+ _image(image)
+{
+ // empty
+}
+
+bool
+VerbatimDirective::matches(const IHopDirective &dir) const
+{
+ if (dir.getType() != TYPE_VERBATIM) {
+ return false;
+ }
+ return _image == static_cast<const VerbatimDirective&>(dir)._image;
+}
+
+string
+VerbatimDirective::toString() const
+{
+ return _image;
+}
+
+string
+VerbatimDirective::toDebugString() const
+{
+ return vespalib::make_vespa_string("VerbatimDirective(image = '%s')",
+ _image.c_str());
+}
+
+} // mbus
diff --git a/messagebus/src/vespa/messagebus/routing/verbatimdirective.h b/messagebus/src/vespa/messagebus/routing/verbatimdirective.h
new file mode 100644
index 00000000000..41ebae497f9
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/routing/verbatimdirective.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "ihopdirective.h"
+
+namespace mbus {
+
+/**
+ * This class represents a verbatim match within a {@link Hop}'s selector. This is nothing more than a string that will
+ * be used as-is when performing service name lookups.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class VerbatimDirective : public IHopDirective {
+private:
+ string _image;
+
+public:
+ /**
+ * Constructs a new verbatim selector item for a given image.
+ *
+ * @param image The image to assign to this.
+ */
+ VerbatimDirective(const vespalib::stringref &image);
+
+ /**
+ * Returns the image to which this is a verbatim match.
+ *
+ * @return The image.
+ */
+ const string &getImage() const { return _image; }
+
+ virtual Type getType() const { return TYPE_VERBATIM; }
+ virtual bool matches(const IHopDirective &dir) const;
+ virtual string toString() const;
+ virtual string toDebugString() const;
+};
+
+} // mbus
+
diff --git a/messagebus/src/vespa/messagebus/rpcmessagebus.cpp b/messagebus/src/vespa/messagebus/rpcmessagebus.cpp
new file mode 100644
index 00000000000..fc966f896cb
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/rpcmessagebus.cpp
@@ -0,0 +1,38 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".mb");
+#include "rpcmessagebus.h"
+
+namespace mbus {
+
+RPCMessageBus::RPCMessageBus(const MessageBusParams &mbusParams,
+ const RPCNetworkParams &rpcParams,
+ const config::ConfigUri & routingCfgUri) :
+ _net(rpcParams),
+ _bus(_net, mbusParams),
+ _agent(_bus),
+ _subscriber(routingCfgUri.getContext())
+{
+ _subscriber.subscribe(routingCfgUri.getConfigId(), &_agent);
+ _subscriber.start();
+}
+
+RPCMessageBus::RPCMessageBus(const ProtocolSet &protocols,
+ const RPCNetworkParams &rpcParams,
+ const config::ConfigUri &routingCfgUri) :
+ _net(rpcParams),
+ _bus(_net, protocols),
+ _agent(_bus),
+ _subscriber(routingCfgUri.getContext())
+{
+ _subscriber.subscribe(routingCfgUri.getConfigId(), &_agent);
+ _subscriber.start();
+}
+
+RPCMessageBus::~RPCMessageBus()
+{
+ _subscriber.close();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/rpcmessagebus.h b/messagebus/src/vespa/messagebus/rpcmessagebus.h
new file mode 100644
index 00000000000..022a5825110
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/rpcmessagebus.h
@@ -0,0 +1,99 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/messagebus/network/rpcnetwork.h>
+#include <string>
+#include <vespa/config/helper/legacysubscriber.h>
+#include "messagebus.h"
+#include "configagent.h"
+#include "protocolset.h"
+
+namespace mbus {
+
+/**
+ * The RPCMessageBus class wraps a MessageBus with an RPCNetwork and handles
+ * reconfiguration. The RPCMessageBus constructor will perform setup, while the
+ * RPCMessageBus destructor will perform controlled shutdown and cleanup. Please
+ * note that according to the object delete order, you must delete all sessions
+ * before deleting the underlying MessageBus object.
+ */
+class RPCMessageBus : public boost::noncopyable {
+private:
+ RPCNetwork _net;
+ MessageBus _bus;
+ ConfigAgent _agent;
+ config::ConfigFetcher _subscriber;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<RPCMessageBus> UP;
+ typedef std::shared_ptr<RPCMessageBus> SP;
+
+ /**
+ * Constructs a new instance of this class.
+ *
+ * @param mbusParams A complete set of message bus parameters.
+ * @param rpcParams A complete set of network parameters.
+ * @param routingCfgId The config id for message bus routing specs.
+ */
+ RPCMessageBus(const MessageBusParams &mbusParams,
+ const RPCNetworkParams &rpcParams = RPCNetworkParams(),
+ const config::ConfigUri & routingCfgId = config::ConfigUri("client"));
+
+
+ /**
+ * This constructor requires an array of protocols that it is to support, as
+ * well as the host application's config identifier. That identifier is
+ * necessary so that all created sessions can be uniquely identified on the
+ * network.
+ *
+ * @param protocols An array of known protocols.
+ * @param rpcParams A complete set of network parameters.
+ * @param routingCfgId The config id for messagebus routing specs.
+ */
+ RPCMessageBus(const ProtocolSet &protocols,
+ const RPCNetworkParams &rpcParams = RPCNetworkParams(),
+ const config::ConfigUri & routingCfgId = config::ConfigUri("client"));
+
+ /**
+ * Destruct. This will destruct the internal MessageBus and RPCNetwork
+ * objects, thus performing cleanup. Note that all sessions created from the
+ * internal MessageBus object should be destructed before deleting this
+ * object.
+ **/
+ ~RPCMessageBus();
+
+ /**
+ * Returns a reference to the contained message bus object.
+ *
+ * @return The mbus object.
+ */
+ MessageBus &getMessageBus() { return _bus; }
+
+ /**
+ * Returns a const reference to the contained message bus object.
+ *
+ * @return The mbus object.
+ */
+ const MessageBus &getMessageBus() const { return _bus; }
+
+ /**
+ * Returns a reference to the contained rpc network object.
+ *
+ * @return The rpc network.
+ */
+ RPCNetwork &getRPCNetwork() { return _net; }
+
+ /**
+ * Returns a const reference to the contained rpc network object.
+ *
+ * @return The rpc network.
+ */
+ const RPCNetwork &getRPCNetwork() const { return _net; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/sendproxy.cpp b/messagebus/src/vespa/messagebus/sendproxy.cpp
new file mode 100644
index 00000000000..be7b4822d0d
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sendproxy.cpp
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".sendproxy");
+
+#include "sendproxy.h"
+
+namespace mbus {
+
+SendProxy::SendProxy(MessageBus &mbus, INetwork &net, Resender *resender) :
+ _mbus(mbus),
+ _net(net),
+ _resender(resender),
+ _msg(),
+ _logTrace(false),
+ _root()
+{
+ // empty
+}
+
+void
+SendProxy::handleMessage(Message::UP msg)
+{
+ Trace &trace = msg->getTrace();
+ if (trace.getLevel() == 0) {
+ if (logger.wants(ns_log::Logger::spam)) {
+ trace.setLevel(9);
+ _logTrace = true;
+ } else if (logger.wants(ns_log::Logger::debug)) {
+ trace.setLevel(6);
+ _logTrace = true;
+ }
+ }
+ _msg = std::move(msg);
+ _root.reset(new RoutingNode(_mbus, _net, _resender, *this, *_msg, this));
+ _root->send();
+}
+
+void
+SendProxy::handleDiscard(Context ctx)
+{
+ (void)ctx;
+ _msg->discard();
+ delete this;
+}
+
+void
+SendProxy::handleReply(Reply::UP reply)
+{
+ Trace &trace = _msg->getTrace();
+ if (_logTrace) {
+ if (reply->hasErrors()) {
+ LOG(debug, "Trace for reply with error(s):\n%s", reply->getTrace().toString().c_str());
+ } else if (logger.wants(ns_log::Logger::spam)) {
+ LOG(spam, "Trace for reply:\n%s", reply->getTrace().toString().c_str());
+ }
+ Trace empty;
+ trace.swap(empty);
+ } else if (trace.getLevel() > 0) {
+ trace.getRoot().addChild(reply->getTrace().getRoot());
+ trace.getRoot().normalize();
+ }
+ reply->swapState(*_msg);
+ reply->setMessage(std::move(_msg));
+
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+
+ delete this;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/sendproxy.h b/messagebus/src/vespa/messagebus/sendproxy.h
new file mode 100644
index 00000000000..36de3ab8aff
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sendproxy.h
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/messagebus/routing/routingtable.h>
+#include <vespa/messagebus/routing/routingnode.h>
+
+namespace mbus {
+
+class MessageBus;
+
+/**
+ * This class owns a message that is being sent by message bus. Once a reply is received, the message is
+ * attached to it and returned to the application. After the reply has been propagated upwards, this object
+ * deletes itself. This also implements the discard policy of {@link RoutingNode}.
+ */
+class SendProxy : public boost::noncopyable,
+ public IDiscardHandler,
+ public IMessageHandler,
+ public IReplyHandler {
+private:
+ MessageBus &_mbus;
+ INetwork &_net;
+ Resender *_resender;
+ Message::UP _msg;
+ bool _logTrace;
+ RoutingNode::UP _root;
+
+public:
+ /**
+ * Constructs a new instance of this class to maintain sending of a single message.
+ *
+ * @param mbus The message bus that owns this.
+ * @param net The network layer to transmit through.
+ * @param resender The resender to use.
+ */
+ SendProxy(MessageBus &mbus, INetwork &net, Resender *resender);
+
+ // Implements IDiscardHandler.
+ void handleDiscard(Context ctx);
+
+ // Implements IMessageHandler.
+ void handleMessage(Message::UP msg);
+
+ // Implements IReplyHandler.
+ void handleReply(Reply::UP reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/sequencer.cpp b/messagebus/src/vespa/messagebus/sequencer.cpp
new file mode 100644
index 00000000000..eca801d4b2e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sequencer.cpp
@@ -0,0 +1,111 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".sequencer");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "sequencer.h"
+#include "tracelevel.h"
+
+namespace mbus {
+
+Sequencer::Sequencer(IMessageHandler &sender) :
+ _lock("mbus::Sequencer::_lock", false),
+ _sender(sender),
+ _seqMap()
+{
+ // empty
+}
+
+Sequencer::~Sequencer()
+{
+ for (QueueMap::iterator it = _seqMap.begin(); it != _seqMap.end(); ++it) {
+ MessageQueue *queue = it->second;
+ if (queue != NULL) {
+ while (queue->size() > 0) {
+ Message *msg = queue->front();
+ queue->pop();
+ msg->discard();
+ delete msg;
+ }
+ delete queue;
+ }
+ }
+}
+
+Message::UP
+Sequencer::filter(Message::UP msg)
+{
+ uint64_t seqId = msg->getSequenceId();
+ msg->setContext(Context(seqId));
+ {
+ vespalib::LockGuard guard(_lock);
+ QueueMap::iterator it = _seqMap.find(seqId);
+ if (it != _seqMap.end()) {
+ if (it->second == NULL) {
+ it->second = new MessageQueue();
+ }
+ msg->getTrace().trace(TraceLevel::COMPONENT,
+ vespalib::make_vespa_string("Sequencer queued message with sequence id '%" PRIu64 "'.", seqId));
+ it->second->push(msg.get());
+ msg.release();
+ return Message::UP();
+ }
+ _seqMap[seqId] = NULL; // insert empty queue
+ }
+ return std::move(msg);
+}
+
+void
+Sequencer::sequencedSend(Message::UP msg)
+{
+ msg->getTrace().trace(TraceLevel::COMPONENT,
+ vespalib::make_vespa_string("Sequencer sending message with sequence id '%" PRIu64 "'.",
+ msg->getContext().value.UINT64));
+ msg->pushHandler(*this);
+ _sender.handleMessage(std::move(msg));
+}
+
+void
+Sequencer::handleMessage(Message::UP msg)
+{
+ if (msg->hasSequenceId()) {
+ msg = filter(std::move(msg));
+ if (msg.get() != NULL) {
+ sequencedSend(std::move(msg));
+ }
+ } else {
+ _sender.handleMessage(std::move(msg)); // unsequenced
+ }
+}
+
+void
+Sequencer::handleReply(Reply::UP reply)
+{
+ uint64_t seq = reply->getContext().value.UINT64;
+ reply->getTrace().trace(TraceLevel::COMPONENT,
+ vespalib::make_vespa_string("Sequencer received reply with sequence id '%" PRIu64 "'.", seq));
+ Message::UP msg;
+ {
+ vespalib::LockGuard guard(_lock);
+ QueueMap::iterator it = _seqMap.find(seq);
+ MessageQueue *que = it->second;
+ LOG_ASSERT(it != _seqMap.end());
+ if (que == NULL || que->size() == 0) {
+ if (que != NULL) {
+ delete que;
+ }
+ _seqMap.erase(it);
+ } else {
+ msg.reset(que->front());
+ que->pop();
+ }
+ }
+ if (msg.get() != NULL) {
+ sequencedSend(std::move(msg));
+ }
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/sequencer.h b/messagebus/src/vespa/messagebus/sequencer.h
new file mode 100644
index 00000000000..aee26c0a9ca
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sequencer.h
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <boost/utility.hpp>
+#include <map>
+#include <vespa/vespalib/util/sync.h>
+#include "imessagehandler.h"
+#include "ireplyhandler.h"
+#include "message.h"
+#include "reply.h"
+#include "queue.h"
+
+namespace mbus {
+
+/**
+ * A Sequencer ensures correct sequencing of pending messages. When a Sequencer is created, it is given an
+ * object implementing the IMessageHandler API to use for sending messages. This class is used by the
+ * SourceSession class and is not intended for external use.
+ */
+class Sequencer : public boost::noncopyable,
+ public IMessageHandler,
+ public IReplyHandler
+{
+private:
+ vespalib::Lock _lock;
+ IMessageHandler &_sender;
+
+ typedef Queue<Message*> MessageQueue;
+ typedef std::map<uint64_t, MessageQueue*> QueueMap;
+ QueueMap _seqMap;
+
+private:
+ /**
+ * Filter a message against the current sequencing state. If the message is returned back out again, it
+ * has been cleared for sending and its sequencing information has been added to the state. If the message
+ * is not returned it has been queued for later sending due to sequencing restrictions. This method also
+ * sets the sequence id as message context.
+ *
+ * @param msg The message to filter.
+ * @return The argument message if it passed the filter.
+ */
+ Message::UP filter(Message::UP msg);
+
+ /**
+ * Internal method for forwarding a sequenced message to the underlying sender.
+ *
+ * @param msg The message to forward.
+ */
+ void sequencedSend(Message::UP msg);
+
+public:
+ /**
+ * Convenience typedef for an auto pointer to a Sequencer object.
+ */
+ typedef std::unique_ptr<Sequencer> UP;
+
+ /**
+ * Create a new Sequencer using the given sender to send messages.
+ *
+ * @param sender The underlying sender.
+ */
+ Sequencer(IMessageHandler &sender);
+
+ /**
+ * Destruct. This will also destruct any Message objects held back due to sequencing collisions.
+ */
+ virtual ~Sequencer();
+
+ /**
+ * All messages pass through this handler when being sent by the owning source session. In case the
+ * message has no sequencing-id, it is simply passed through to the next handler in the chain. Sequenced
+ * messages are sent only if there is no queue for their id, otherwise they are queued.
+ *
+ * @param msg The message to send.
+ */
+ void handleMessage(Message::UP msg);
+
+ /**
+ * Lookup the sequencing id of an incoming reply to pop the front of the corresponding queue, and then
+ * send the next message in line, if any.
+ *
+ * @param reply The reply received.
+ */
+ void handleReply(Reply::UP reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/sourcesession.cpp b/messagebus/src/vespa/messagebus/sourcesession.cpp
new file mode 100644
index 00000000000..a2cea9ddc8a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sourcesession.cpp
@@ -0,0 +1,166 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".sourcesession");
+
+#include <vespa/messagebus/routing/routingtable.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "error.h"
+#include "errorcode.h"
+#include "messagebus.h"
+#include "replygate.h"
+#include "sourcesession.h"
+#include "sourcesessionparams.h"
+#include "tracelevel.h"
+#include <algorithm>
+#include <iostream>
+
+namespace mbus {
+
+SourceSession::SourceSession(MessageBus &mbus, const SourceSessionParams &params)
+ : _monitor("mbus::SourceSession::_monitor", false),
+ _mbus(mbus),
+ _gate(new ReplyGate(_mbus)),
+ _sequencer(*_gate),
+ _replyHandler(params.getReplyHandler()),
+ _throttlePolicy(params.getThrottlePolicy()),
+ _timeout(params.getTimeout()),
+ _pendingCount(0),
+ _closed(false),
+ _done(false)
+{
+ LOG_ASSERT(params.hasReplyHandler());
+}
+
+SourceSession::~SourceSession()
+{
+ // Ensure that no more replies propagate from mbus.
+ _gate->close();
+ _mbus.sync();
+
+ // Tell gate that we will no longer use it.
+ _gate->subRef();
+}
+
+Result
+SourceSession::send(Message::UP msg, const string &routeName, bool parseIfNotFound)
+{
+ bool found = false;
+ RoutingTable::SP rt = _mbus.getRoutingTable(msg->getProtocol());
+ if (rt.get() != NULL) {
+ const Route *route = rt->getRoute(routeName);
+ if (route != NULL) {
+ msg->setRoute(*route);
+ found = true;
+ } else if (!parseIfNotFound) {
+ string str = vespalib::make_vespa_string(
+ "Route '%s' not found.",
+ routeName.c_str());
+ return Result(Error(ErrorCode::ILLEGAL_ROUTE, str), std::move(msg));
+ }
+ } else if (!parseIfNotFound) {
+ string str = vespalib::make_vespa_string(
+ "No routing table available for protocol '%s'.",
+ msg->getProtocol().c_str());
+ return Result(Error(ErrorCode::ILLEGAL_ROUTE, str), std::move(msg));
+ }
+ if (!found) {
+ msg->setRoute(Route::parse(routeName));
+ }
+ return send(std::move(msg));
+}
+
+Result
+SourceSession::send(Message::UP msg, const Route &route)
+{
+ msg->setRoute(route);
+ return send(std::move(msg));
+}
+
+Result
+SourceSession::send(Message::UP msg)
+{
+ msg->setTimeReceivedNow();
+ if (msg->getTimeRemaining() == 0) {
+ msg->setTimeRemaining((uint64_t)(_timeout * 1000));
+ }
+ {
+ vespalib::MonitorGuard guard(_monitor);
+ if (_closed) {
+ return Result(Error(ErrorCode::SEND_QUEUE_CLOSED,
+ "Source session is closed."), std::move(msg));
+ }
+ if (_throttlePolicy.get() != NULL && !_throttlePolicy->canSend(*msg, _pendingCount)) {
+ return Result(Error(ErrorCode::SEND_QUEUE_FULL,
+ vespalib::make_vespa_string("Too much pending data (%d messages).",
+ _pendingCount)), std::move(msg));
+ }
+ msg->pushHandler(_replyHandler);
+ if (_throttlePolicy.get() != NULL) {
+ _throttlePolicy->processMessage(*msg);
+ }
+ ++_pendingCount;
+ }
+ if (msg->getTrace().shouldTrace(TraceLevel::COMPONENT)) {
+ msg->getTrace().trace(TraceLevel::COMPONENT,
+ vespalib::make_vespa_string("Source session accepted a %d byte message. "
+ "%d message(s) now pending.",
+ msg->getApproxSize(), _pendingCount));
+ }
+ msg->pushHandler(*this);
+ _sequencer.handleMessage(std::move(msg));
+ return Result();
+}
+
+void
+SourceSession::handleReply(Reply::UP reply)
+{
+ bool done;
+ {
+ vespalib::MonitorGuard guard(_monitor);
+ LOG_ASSERT(_pendingCount > 0);
+ --_pendingCount;
+ if (_throttlePolicy.get() != NULL) {
+ _throttlePolicy->processReply(*reply);
+ }
+ done = (_closed && _pendingCount == 0);
+ }
+ if (reply->getTrace().shouldTrace(TraceLevel::COMPONENT)) {
+ reply->getTrace().trace(TraceLevel::COMPONENT,
+ vespalib::make_vespa_string("Source session received reply. "
+ "%d message(s) now pending.",
+ _pendingCount));
+ }
+ IReplyHandler &handler = reply->getCallStack().pop(*reply);
+ handler.handleReply(std::move(reply));
+ if (done) {
+ vespalib::MonitorGuard guard(_monitor);
+ LOG_ASSERT(_pendingCount == 0);
+ LOG_ASSERT(_closed);
+ _done = true;
+ guard.broadcast();
+ }
+}
+
+void
+SourceSession::close()
+{
+ vespalib::MonitorGuard guard(_monitor);
+ _closed = true;
+ if (_pendingCount == 0) {
+ _done = true;
+ }
+ while (!_done) {
+ guard.wait();
+ }
+}
+
+SourceSession &
+SourceSession::setTimeout(double timeout)
+{
+ vespalib::MonitorGuard guard(_monitor);
+ _timeout = timeout;
+ return *this;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/sourcesession.h b/messagebus/src/vespa/messagebus/sourcesession.h
new file mode 100644
index 00000000000..af2c4ff6c92
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sourcesession.h
@@ -0,0 +1,128 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/vespalib/util/sync.h>
+#include "ireplyhandler.h"
+#include "result.h"
+#include "sequencer.h"
+#include "sourcesessionparams.h"
+
+namespace mbus {
+
+class MessageBus;
+class ReplyGate;
+
+/**
+ * A SourceSession is used to send Message objects along a named or explicitly defined route and get Reply
+ * objects back. A source session does not have a service name and can only receive replies to the messages
+ * sent on it.
+ **/
+class SourceSession : public boost::noncopyable, public IReplyHandler {
+private:
+ friend class MessageBus;
+
+ vespalib::Monitor _monitor;
+ MessageBus &_mbus;
+ ReplyGate *_gate;
+ Sequencer _sequencer;
+ IReplyHandler &_replyHandler;
+ IThrottlePolicy::SP _throttlePolicy;
+ double _timeout;
+ uint32_t _pendingCount;
+ bool _closed;
+ bool _done;
+
+private:
+ /**
+ * This is the private constructor used by mbus to create source sessions. It expects all arguments but
+ * the {@link SourceSessionParams} to be proper, so no checks are performed.
+ *
+ * @param mbus The message bus that created this instance.
+ * @param params A parameter object that holds configuration parameters.
+ */
+ SourceSession(MessageBus &mbus, const SourceSessionParams &params);
+
+public:
+ /**
+ * Convenience typedef for an auto pointer to a SourceSession object.
+ **/
+ typedef std::unique_ptr<SourceSession> UP;
+
+ /**
+ * The destructor untangles from messagebus. This is safe, but you will loose the replies of all pending
+ * messages. After this method returns, messagebus will not invoke any handlers associated with this
+ * session.
+ **/
+ virtual ~SourceSession();
+
+ /**
+ * This is a convenience function to assign a named route to the given message, and then pass it to the
+ * other {@link #send(Message)} method of this session. If the route could not be found this methods
+ * returns with an appropriate error, unless the 'parseIfNotFound' argument is true. In that case, the
+ * route name is passed through to the Route factory method {@link Route#create}.
+ *
+ * @param msg The message to send.
+ * @param routeName The route to assign to the message.
+ * @param parseIfNotFound Whether or not to parse routeName as a route if it could not be found.
+ * @return The immediate result of the attempt to send this message.
+ */
+ Result send(Message::UP msg, const string &routeName, bool parseIfNotFound = false);
+
+ /**
+ * This is a convenience function to assign a given route to the given message, and then pass it to the
+ * other {@link #send(Message)} method of this session.
+ *
+ * @param msg The message to send.
+ * @param route The route to assign to the message.
+ * @return The immediate result of the attempt to send this message.
+ */
+ Result send(Message::UP msg, const Route &route);
+
+ /**
+ * Send a Message along a route that has already been specified in the message object.
+ *
+ * @return send result
+ * @param msg the message to send
+ */
+ Result send(Message::UP msg);
+
+ /**
+ * Handle a Reply obtained from messagebus.
+ *
+ * @param reply the Reply
+ **/
+ void handleReply(Reply::UP reply);
+
+ /**
+ * Close this session. This method will block until Reply objects have been obtained for all pending
+ * Message objects. Also, no more Message objects will be accepted by this session after closing has
+ * initiated.
+ **/
+ void close();
+
+ /**
+ * Returns the reply handler of this session.
+ *
+ * @return The reply handler.
+ */
+ IReplyHandler &getReplyHandler() { return _replyHandler; }
+
+ /**
+ * Returns the number of messages sent that have not been replied to yet.
+ *
+ * @return The pending count.
+ */
+ uint32_t getPendingCount() const { return _pendingCount; }
+
+ /**
+ * Sets the number of seconds a message can be attempted sent until it times out.
+ *
+ * @param timeout The numer of seconds allowed.
+ * @return This, to allow chaining.
+ */
+ SourceSession &setTimeout(double timeout);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/sourcesessionparams.cpp b/messagebus/src/vespa/messagebus/sourcesessionparams.cpp
new file mode 100644
index 00000000000..ae73b462660
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sourcesessionparams.cpp
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".sourcesessionparams");
+
+#include "dynamicthrottlepolicy.h"
+#include "sourcesessionparams.h"
+
+namespace mbus {
+
+SourceSessionParams::SourceSessionParams() :
+ _replyHandler(NULL),
+ _throttlePolicy(new DynamicThrottlePolicy()),
+ _timeout(180.0)
+{
+ // empty
+}
+
+IThrottlePolicy::SP
+SourceSessionParams::getThrottlePolicy() const
+{
+ return _throttlePolicy;
+}
+
+SourceSessionParams &
+SourceSessionParams::setThrottlePolicy(IThrottlePolicy::SP throttlePolicy)
+{
+ _throttlePolicy = throttlePolicy;
+ return *this;
+}
+
+double
+SourceSessionParams::getTimeout() const
+{
+ return _timeout;
+}
+
+SourceSessionParams &
+SourceSessionParams::setTimeout(double timeout)
+{
+ _timeout = timeout;
+ return *this;
+}
+
+bool
+SourceSessionParams::hasReplyHandler() const
+{
+ return _replyHandler != NULL;
+}
+
+IReplyHandler &
+SourceSessionParams::getReplyHandler() const
+{
+ return *_replyHandler;
+}
+
+SourceSessionParams &
+SourceSessionParams::setReplyHandler(IReplyHandler &handler)
+{
+ _replyHandler = &handler;
+ return *this;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/sourcesessionparams.h b/messagebus/src/vespa/messagebus/sourcesessionparams.h
new file mode 100644
index 00000000000..3c45f68bd10
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/sourcesessionparams.h
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "ireplyhandler.h"
+#include "ithrottlepolicy.h"
+
+namespace mbus {
+
+/**
+ * To facilitate several configuration parameters to the {@link MessageBus#createSourceSession(ReplyHandler,
+ * SourceSessionParams)}, all parameters are held by this class. This class has reasonable default values for each
+ * parameter.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @version $Id$
+ */
+class SourceSessionParams {
+private:
+ IReplyHandler *_replyHandler;
+ IThrottlePolicy::SP _throttlePolicy;
+ double _timeout;
+
+public:
+ /**
+ * This constructor will set default values for all parameters.
+ */
+ SourceSessionParams();
+
+ /**
+ * Returns the policy to use for throttling output.
+ *
+ * @return The policy.
+ */
+ IThrottlePolicy::SP getThrottlePolicy() const;
+
+ /**
+ * Sets the policy to use for throttling output.
+ *
+ * @param throttlePolicy The policy to set.
+ * @return This, to allow chaining.
+ */
+ SourceSessionParams &setThrottlePolicy(IThrottlePolicy::SP throttlePolicy);
+
+ /**
+ * Returns the total timeout parameter.
+ *
+ * @return The total timeout parameter.
+ */
+ double getTimeout() const;
+
+ /**
+ * Returns the number of seconds a message can spend trying to succeed.
+ *
+ * @return The timeout in seconds.
+ */
+ SourceSessionParams &setTimeout(double timeout);
+
+ /**
+ * Returns whether or not a reply handler has been assigned to this.
+ *
+ * @return True if a handler is set.
+ */
+ bool hasReplyHandler() const;
+
+ /**
+ * Returns the handler to receive incoming replies. If you call this method without first assigning a
+ * reply handler to this object, you wil de-ref null.
+ *
+ * @return The handler.
+ */
+ IReplyHandler &getReplyHandler() const;
+
+ /**
+ * Sets the handler to receive incoming replies.
+ *
+ * @param handler The handler to set.
+ * @return This, to allow chaining.
+ */
+ SourceSessionParams &setReplyHandler(IReplyHandler &handler);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/staticthrottlepolicy.cpp b/messagebus/src/vespa/messagebus/staticthrottlepolicy.cpp
new file mode 100644
index 00000000000..d26362e9072
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/staticthrottlepolicy.cpp
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include "staticthrottlepolicy.h"
+
+namespace mbus {
+
+StaticThrottlePolicy::StaticThrottlePolicy() :
+ _maxPendingCount(0),
+ _maxPendingSize(0),
+ _pendingSize(0)
+{
+ // empty
+}
+
+uint32_t
+StaticThrottlePolicy::getMaxPendingCount() const
+{
+ return _maxPendingCount;
+}
+
+StaticThrottlePolicy &
+StaticThrottlePolicy::setMaxPendingCount(uint32_t maxCount)
+{
+ _maxPendingCount = maxCount;
+ return *this;
+}
+
+uint64_t
+StaticThrottlePolicy::getMaxPendingSize() const
+{
+ return _maxPendingSize;
+}
+
+StaticThrottlePolicy &
+StaticThrottlePolicy::setMaxPendingSize(uint64_t maxSize)
+{
+ _maxPendingSize = maxSize;
+ return *this;
+}
+
+uint64_t
+StaticThrottlePolicy::getPendingSize() const
+{
+ return _pendingSize;
+}
+
+bool
+StaticThrottlePolicy::canSend(const Message &msg, uint32_t pendingCount)
+{
+ if (_maxPendingCount > 0 && pendingCount >= _maxPendingCount) {
+ return false;
+ }
+ if (_maxPendingSize > 0 && _pendingSize >= _maxPendingSize) {
+ return false;
+ }
+ (void)msg;
+ return true;
+}
+
+void
+StaticThrottlePolicy::processMessage(Message &msg)
+{
+ uint32_t size = msg.getApproxSize();
+ msg.setContext(Context((uint64_t)size));
+ _pendingSize += size;
+}
+
+void
+StaticThrottlePolicy::processReply(Reply &reply)
+{
+ uint32_t size = reply.getContext().value.UINT64;
+ _pendingSize -= size;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/staticthrottlepolicy.h b/messagebus/src/vespa/messagebus/staticthrottlepolicy.h
new file mode 100644
index 00000000000..e6d75dfd3c7
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/staticthrottlepolicy.h
@@ -0,0 +1,84 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "ithrottlepolicy.h"
+
+namespace mbus {
+
+/**
+ * This is an implementatin of the {@link ThrottlePolicy} that offers static limits to the amount of pending
+ * data a {@link SourceSession} is allowed to have. You may choose to set a limit to the total number of
+ * pending messages (by way of {@link #setMaxPendingCount(int)}), the total size of pending messages (by way
+ * of {@link #setMaxPendingSize(long)}), or some combination thereof.
+ *
+ * <b>NOTE:</b> By context, "pending" is refering to the number of sent messages that have not been replied to
+ * yet.
+ */
+class StaticThrottlePolicy: public IThrottlePolicy {
+private:
+ uint32_t _maxPendingCount;
+ uint64_t _maxPendingSize;
+ uint64_t _pendingSize;
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::unique_ptr<StaticThrottlePolicy> UP;
+ typedef std::shared_ptr<StaticThrottlePolicy> SP;
+
+ /**
+ * Constructs a new instance of this policy and sets the appropriate default values of member data.
+ */
+ StaticThrottlePolicy();
+
+ /**
+ * Returns the maximum number of pending messages allowed.
+ *
+ * @return The max limit.
+ */
+ uint32_t getMaxPendingCount() const;
+
+ /**
+ * Sets the maximum number of pending messages allowed.
+ *
+ * @param maxCount The max count.
+ * @return This, to allow chaining.
+ */
+ StaticThrottlePolicy &setMaxPendingCount(uint32_t maxCount);
+
+ /**
+ * Returns the maximum total size of pending messages allowed.
+ *
+ * @return The max limit.
+ */
+ uint64_t getMaxPendingSize() const;
+
+ /**
+ * Sets the maximum total size of pending messages allowed. This size is relative to the value returned by
+ * {@link com.yahoo.messagebus.Message#getApproxSize()}.
+ *
+ * @param maxSize The max size.
+ * @return This, to allow chaining.
+ */
+ StaticThrottlePolicy &setMaxPendingSize(uint64_t maxSize);
+
+ /**
+ * Returns the total size of pending messages.
+ *
+ * @return The size.
+ */
+ uint64_t getPendingSize() const;
+
+ // Implements IThrottlePolicy.
+ bool canSend(const Message &msg, uint32_t pendingCount);
+
+ // Implements IThrottlePolicy.
+ void processMessage(Message &msg);
+
+ // Implements IThrottlePolicy.
+ void processReply(Reply &reply);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/systemtimer.cpp b/messagebus/src/vespa/messagebus/systemtimer.cpp
new file mode 100644
index 00000000000..35f8b22f1b8
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/systemtimer.cpp
@@ -0,0 +1,15 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include "systemtimer.h"
+
+namespace mbus {
+
+uint64_t
+SystemTimer::getMilliTime() const
+{
+ FastOS_Time time;
+ time.SetNow();
+ return (uint64_t)time.MilliSecs();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/systemtimer.h b/messagebus/src/vespa/messagebus/systemtimer.h
new file mode 100644
index 00000000000..b03f847718b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/systemtimer.h
@@ -0,0 +1,20 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "itimer.h"
+
+namespace mbus {
+
+/**
+ * This is the implementation of the {@link Timer} interface that all time-based
+ * constructs in message bus use by default. The only reason for replacing this
+ * is for writing unit tests.
+ */
+class SystemTimer : public ITimer {
+public:
+ // Implements ITimer.
+ uint64_t getMilliTime() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/.gitignore b/messagebus/src/vespa/messagebus/testlib/.gitignore
new file mode 100644
index 00000000000..ae41f308bef
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+/libmessagebus-test.so.5.1
diff --git a/messagebus/src/vespa/messagebus/testlib/CMakeLists.txt b/messagebus/src/vespa/messagebus/testlib/CMakeLists.txt
new file mode 100644
index 00000000000..4c930bfd9aa
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(messagebus_messagebus-test
+ SOURCES
+ custompolicy.cpp
+ oosserver.cpp
+ oosstate.cpp
+ receptor.cpp
+ simplemessage.cpp
+ simpleprotocol.cpp
+ simplereply.cpp
+ slobrok.cpp
+ slobrokstate.cpp
+ testserver.cpp
+ INSTALL lib64
+ DEPENDS
+ slobrok_slobrokserver
+)
diff --git a/messagebus/src/vespa/messagebus/testlib/create-class-cpp.sh b/messagebus/src/vespa/messagebus/testlib/create-class-cpp.sh
new file mode 100755
index 00000000000..f7c209427a8
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/create-class-cpp.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+class=$1
+guard=`echo $class | tr 'a-z' 'A-Z'`
+name=`echo $class | tr 'A-Z' 'a-z'`
+
+cat <<EOF
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/log.h>
+LOG_SETUP(".$name");
+#include <vespa/fastos/fastos.h>
+#include "$name.h"
+
+namespace mbus {
+
+$class::$class()
+{
+}
+
+$class::~$class()
+{
+}
+
+} // namespace mbus
+EOF
diff --git a/messagebus/src/vespa/messagebus/testlib/create-class-h.sh b/messagebus/src/vespa/messagebus/testlib/create-class-h.sh
new file mode 100755
index 00000000000..c4df87c4f8a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/create-class-h.sh
@@ -0,0 +1,26 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+#!/bin/sh
+
+class=$1
+guard=`echo $class | tr 'a-z' 'A-Z'`
+
+cat <<EOF
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+namespace mbus {
+
+class $class
+{
+private:
+ $class(const $class &);
+ $class &operator=(const $class &);
+public:
+ $class();
+ virtual ~$class();
+};
+
+} // namespace mbus
+
+EOF
diff --git a/messagebus/src/vespa/messagebus/testlib/create-interface.sh b/messagebus/src/vespa/messagebus/testlib/create-interface.sh
new file mode 100755
index 00000000000..e6f93b24355
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/create-interface.sh
@@ -0,0 +1,22 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+#!/bin/sh
+
+class=$1
+guard=`echo $class | tr 'a-z' 'A-Z'`
+
+cat <<EOF
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+namespace mbus {
+
+class $class
+{
+public:
+ virtual ~$class() {}
+};
+
+} // namespace mbus
+
+EOF
diff --git a/messagebus/src/vespa/messagebus/testlib/custompolicy.cpp b/messagebus/src/vespa/messagebus/testlib/custompolicy.cpp
new file mode 100644
index 00000000000..4d26732067d
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/custompolicy.cpp
@@ -0,0 +1,143 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".custompolicy");
+
+#include <boost/tokenizer.hpp>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+#include "custompolicy.h"
+#include "simpleprotocol.h"
+
+namespace mbus {
+
+CustomPolicy::CustomPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> consumableErrors,
+ const std::vector<Route> &routes) :
+ _selectOnRetry(selectOnRetry),
+ _consumableErrors(consumableErrors),
+ _routes(routes)
+{
+ // empty
+}
+
+void
+CustomPolicy::select(RoutingContext &context)
+{
+ string str = "Selecting { ";
+ for (uint32_t i = 0; i < _routes.size(); ++i) {
+ str.append("'");
+ str.append(_routes[i].toString());
+ str.append("'");
+ if (i < _routes.size() - 1) {
+ str.append(", ");
+ }
+ }
+ str.append(" }.");
+ context.trace(1, str);
+ context.setSelectOnRetry(_selectOnRetry);
+ for (std::vector<uint32_t>::iterator it = _consumableErrors.begin();
+ it != _consumableErrors.end(); ++it)
+ {
+ context.addConsumableError(*it);
+ }
+ context.addChildren(_routes);
+}
+
+void
+CustomPolicy::merge(RoutingContext &context)
+{
+ Reply::UP ret(new EmptyReply());
+ std::vector<string> routes;
+ for (RoutingNodeIterator it = context.getChildIterator();
+ it.isValid(); it.next())
+ {
+ routes.push_back(it.getRoute().toString());
+ const Reply &reply = it.getReplyRef();
+ for (uint32_t i = 0; i < reply.getNumErrors(); ++i) {
+ ret->addError(reply.getError(i));
+ }
+ }
+ context.setReply(std::move(ret));
+ string str = "Merged { ";
+ for (uint32_t i = 0; i < routes.size(); ++i) {
+ str.append("'");
+ str.append(routes[i]);
+ str.append("'");
+ if (i < _routes.size() - 1) {
+ str.append(", ");
+ }
+ }
+ str.append(" }.");
+ context.trace(1, str);
+}
+
+
+CustomPolicyFactory::CustomPolicyFactory() :
+ _selectOnRetry(true),
+ _consumableErrors()
+{
+ // empty
+}
+
+CustomPolicyFactory::CustomPolicyFactory(bool selectOnRetry) :
+ _selectOnRetry(selectOnRetry),
+ _consumableErrors()
+{
+ // empty
+}
+
+CustomPolicyFactory::CustomPolicyFactory(bool selectOnRetry, uint32_t consumableError) :
+ _selectOnRetry(selectOnRetry),
+ _consumableErrors()
+{
+ _consumableErrors.push_back(consumableError);
+}
+
+CustomPolicyFactory::CustomPolicyFactory(bool selectOnRetry, const std::vector<uint32_t> consumableErrors) :
+ _selectOnRetry(selectOnRetry),
+ _consumableErrors(consumableErrors)
+{
+ // empty
+}
+
+IRoutingPolicy::UP
+CustomPolicyFactory::create(const string &param)
+{
+ string str = "{ ";
+ for (uint32_t i = 0; i < _consumableErrors.size(); ++i) {
+ str.append(ErrorCode::getName(_consumableErrors[i]));
+ if (i < _consumableErrors.size() - 1) {
+ str.append(", ");
+ }
+ }
+ str.append(" }");
+
+ std::vector<Route> routes;
+ parseRoutes(param, routes);
+
+ LOG(info, "Creating custom policy; selectOnRetry = %d, consumableErrors = %s, param = '%s'.",
+ _selectOnRetry, str.c_str(), param.c_str());
+ IRoutingPolicy::UP ret(new CustomPolicy(_selectOnRetry, _consumableErrors, routes));
+ return ret;
+}
+
+
+void
+CustomPolicyFactory::parseRoutes(const string &str,
+ std::vector<Route> &routes)
+{
+ typedef boost::char_separator<char> Separator;
+ typedef boost::tokenizer<Separator> Tokenizer;
+ Separator separator(",");
+ Tokenizer tokenizer(str, separator);
+ for (Tokenizer::iterator it = tokenizer.begin();
+ it != tokenizer.end(); ++it)
+ {
+ routes.push_back(Route::parse(*it));
+ }
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/custompolicy.h b/messagebus/src/vespa/messagebus/testlib/custompolicy.h
new file mode 100644
index 00000000000..d054706312b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/custompolicy.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <boost/utility.hpp>
+#include <vespa/messagebus/routing/iroutingpolicy.h>
+#include "simpleprotocol.h"
+
+namespace mbus {
+
+class CustomPolicy : public boost::noncopyable, public IRoutingPolicy {
+private:
+ bool _selectOnRetry;
+ std::vector<uint32_t> _consumableErrors;
+ std::vector<Route> _routes;
+
+public:
+ CustomPolicy(bool selectOnRetry,
+ const std::vector<uint32_t> consumableErrors,
+ const std::vector<Route> &routes);
+
+ virtual void select(RoutingContext &context);
+ virtual void merge(RoutingContext &context);
+};
+
+class CustomPolicyFactory : public SimpleProtocol::IPolicyFactory {
+private:
+ bool _selectOnRetry;
+ std::vector<uint32_t> _consumableErrors;
+
+public:
+ CustomPolicyFactory();
+ CustomPolicyFactory(bool selectOnRetry);
+ CustomPolicyFactory(bool selectOnRetry, uint32_t consumableError);
+ CustomPolicyFactory(bool selectOnRetry, const std::vector<uint32_t> consumableErrors);
+
+ IRoutingPolicy::UP create(const string &param);
+ static void parseRoutes(const string &str, std::vector<Route> &routes);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/oosserver.cpp b/messagebus/src/vespa/messagebus/testlib/oosserver.cpp
new file mode 100644
index 00000000000..bf7eefbb84a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/oosserver.cpp
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".oosserver");
+#include "oosserver.h"
+#include "slobrok.h"
+
+namespace mbus {
+
+OOSServer::OOSServer(const Slobrok &slobrok, const string service,
+ const OOSState &state)
+ : _lock("mbus::OOSServer::_lock", false),
+ _orb(),
+ _port(0),
+ _regAPI(_orb, slobrok::ConfiguratorFactory(slobrok.config())),
+ _genCnt(1),
+ _state()
+{
+ setState(state);
+ {
+ FRT_ReflectionBuilder rb(&_orb);
+ //-------------------------------------------------------------------
+ rb.DefineMethod("fleet.getOOSList", "ii", "Si", true,
+ FRT_METHOD(OOSServer::rpc_poll), this);
+ rb.MethodDesc("fetch OOS information");
+ rb.ParamDesc("gencnt", "generation already known by client");
+ rb.ParamDesc("timeout", "How many milliseconds to wait for changes "
+ "before returning if nothing has changed (max=10000)");
+ rb.ReturnDesc("names", "list of services that are OOS "
+ "(empty if generation has not changed)");
+ rb.ReturnDesc("newgen", "generation of the returned list");
+ //-------------------------------------------------------------------
+ }
+ _orb.Listen(0);
+ _port = _orb.GetListenPort();
+ _orb.Start();
+ _regAPI.registerName(service);
+}
+
+OOSServer::~OOSServer()
+{
+ _orb.ShutDown(true);
+}
+
+int
+OOSServer::port() const
+{
+ return _port;
+}
+
+void
+OOSServer::rpc_poll(FRT_RPCRequest *req)
+{
+ vespalib::LockGuard guard(_lock);
+ FRT_Values &dst = *req->GetReturn();
+ FRT_StringValue *names = dst.AddStringArray(_state.size());
+ for (uint32_t i = 0; i < _state.size(); ++i) {
+ dst.SetString(&names[i], _state[i].c_str());
+ }
+ dst.AddInt32(_genCnt);
+}
+
+void
+OOSServer::setState(const OOSState &state)
+{
+ std::vector<string> newState;
+ for (OOSState::ITR itr = state.begin();
+ itr != state.end(); ++itr)
+ {
+ if (itr->second) {
+ newState.push_back(itr->first);
+ }
+ }
+ vespalib::LockGuard guard(_lock);
+ _state = newState;
+ ++_genCnt;
+ if (_genCnt == 0) {
+ ++_genCnt;
+ }
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/oosserver.h b/messagebus/src/vespa/messagebus/testlib/oosserver.h
new file mode 100644
index 00000000000..a9ab128aa5d
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/oosserver.h
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/sync.h>
+#include <vespa/fnet/frt/frt.h>
+#include <vespa/slobrok/sbregister.h>
+#include <string>
+#include <vector>
+#include "oosstate.h"
+
+namespace mbus {
+
+class Slobrok;
+
+class OOSServer : public FRT_Invokable
+{
+private:
+ OOSServer(const OOSServer &);
+ OOSServer &operator=(const OOSServer &);
+
+ vespalib::Lock _lock;
+ FRT_Supervisor _orb;
+ int _port;
+ slobrok::api::RegisterAPI _regAPI;
+ uint32_t _genCnt;
+ std::vector<string> _state;
+
+public:
+ OOSServer(const Slobrok &slobrok, const string service,
+ const OOSState &state = OOSState());
+ ~OOSServer();
+ int port() const;
+ void rpc_poll(FRT_RPCRequest *req);
+ void setState(const OOSState &state);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/oosstate.cpp b/messagebus/src/vespa/messagebus/testlib/oosstate.cpp
new file mode 100644
index 00000000000..c4ad1fcafcd
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/oosstate.cpp
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".oosstate");
+#include "oosstate.h"
+
+namespace mbus {
+
+OOSState::OOSState()
+ : _data()
+{
+}
+
+OOSState &
+OOSState::add(const string &service, bool oos)
+{
+ _data.push_back(std::make_pair(service, oos));
+ return *this;
+}
+
+OOSState::ITR
+OOSState::begin() const
+{
+ return _data.begin();
+}
+
+OOSState::ITR
+OOSState::end() const
+{
+ return _data.end();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/oosstate.h b/messagebus/src/vespa/messagebus/testlib/oosstate.h
new file mode 100644
index 00000000000..b083d1fc8af
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/oosstate.h
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vector>
+#include <vespa/messagebus/common.h>
+
+namespace mbus {
+
+class OOSState
+{
+public:
+ typedef std::vector<std::pair<string, bool> > TYPE;
+ typedef TYPE::const_iterator ITR;
+
+private:
+ TYPE _data;
+
+public:
+ OOSState();
+ OOSState &add(const string &service, bool oos = true);
+ ITR begin() const;
+ ITR end() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/receptor.cpp b/messagebus/src/vespa/messagebus/testlib/receptor.cpp
new file mode 100644
index 00000000000..2e45975cac6
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/receptor.cpp
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".receptor");
+#include "receptor.h"
+
+namespace mbus {
+
+Receptor::Receptor()
+ : IMessageHandler(),
+ IReplyHandler(),
+ _mon("mbus::Receptor::_mon", true),
+ _msg(),
+ _reply()
+{
+}
+
+void
+Receptor::handleMessage(Message::UP msg)
+{
+ vespalib::MonitorGuard guard(_mon);
+ _msg = std::move(msg);
+ guard.broadcast();
+}
+
+void
+Receptor::handleReply(Reply::UP reply)
+{
+ vespalib::MonitorGuard guard(_mon);
+ _reply = std::move(reply);
+ guard.broadcast();
+}
+
+Message::UP
+Receptor::getMessage(double maxWait)
+{
+ int ms = (int)(maxWait * 1000);
+ FastOS_Time startTime;
+ startTime.SetNow();
+ vespalib::MonitorGuard guard(_mon);
+ while (_msg.get() == 0) {
+ int w = ms - (int)startTime.MilliSecsToNow();
+ if (w <= 0 || !guard.wait(w)) {
+ break;
+ }
+ }
+ return std::move(_msg);
+}
+
+Reply::UP
+Receptor::getReply(double maxWait)
+{
+ int ms = (int)(maxWait * 1000);
+ FastOS_Time startTime;
+ startTime.SetNow();
+ vespalib::MonitorGuard guard(_mon);
+ while (_reply.get() == 0) {
+ int w = ms - (int)startTime.MilliSecsToNow();
+ if (w <= 0 || !guard.wait(w)) {
+ break;
+ }
+ }
+ return std::move(_reply);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/receptor.h b/messagebus/src/vespa/messagebus/testlib/receptor.h
new file mode 100644
index 00000000000..d7a0376596e
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/receptor.h
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/sync.h>
+#include <vespa/messagebus/imessagehandler.h>
+#include <vespa/messagebus/ireplyhandler.h>
+
+namespace mbus {
+
+class Receptor : public IMessageHandler,
+ public IReplyHandler
+{
+private:
+ vespalib::Monitor _mon;
+ Message::UP _msg;
+ Reply::UP _reply;
+
+ Receptor(const Receptor &);
+ Receptor &operator=(const Receptor &);
+public:
+ Receptor();
+ virtual void handleMessage(Message::UP msg);
+ virtual void handleReply(Reply::UP reply);
+ Message::UP getMessage(double maxWait = 120.0);
+ Reply::UP getReply(double maxWait = 120.0);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/simplemessage.cpp b/messagebus/src/vespa/messagebus/testlib/simplemessage.cpp
new file mode 100644
index 00000000000..947df5100af
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/simplemessage.cpp
@@ -0,0 +1,87 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".simplemessage");
+#include "simplemessage.h"
+#include "simpleprotocol.h"
+
+namespace mbus {
+
+SimpleMessage::SimpleMessage(const string &str) :
+ Message(),
+ _value(str),
+ _hasSeqId(false),
+ _seqId(0)
+{
+ // empty
+}
+
+SimpleMessage::SimpleMessage(const string &str, bool hasSeqId, uint64_t seqId) :
+ Message(),
+ _value(str),
+ _hasSeqId(hasSeqId),
+ _seqId(seqId)
+{
+ // empty
+}
+
+SimpleMessage::~SimpleMessage()
+{
+ // empty
+}
+
+void
+SimpleMessage::setValue(const string &value)
+{
+ _value = value;
+}
+
+const string &
+SimpleMessage::getValue() const
+{
+ return _value;
+}
+
+int
+SimpleMessage::getHash() const
+{
+ int hash = 0;
+ string str = _value;
+ for (uint32_t i = 0; i < str.size(); ++i) {
+ hash += (hash << 9) + (hash >> 7) + (str[i] << 5) + (str[i] >> 3);
+ }
+ return hash;
+}
+
+const string &
+SimpleMessage::getProtocol() const
+{
+ return SimpleProtocol::NAME;
+}
+
+uint32_t
+SimpleMessage::getType() const
+{
+ return SimpleProtocol::MESSAGE;
+}
+
+bool
+SimpleMessage::hasSequenceId() const
+{
+ return _hasSeqId;
+}
+
+uint64_t
+SimpleMessage::getSequenceId() const
+{
+ return _seqId;
+}
+
+uint32_t
+SimpleMessage::getApproxSize() const
+{
+ return _value.size();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/simplemessage.h b/messagebus/src/vespa/messagebus/testlib/simplemessage.h
new file mode 100644
index 00000000000..c709397a89f
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/simplemessage.h
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/messagebus/message.h>
+
+namespace mbus {
+
+class SimpleMessage : public Message {
+private:
+ string _value;
+ bool _hasSeqId;
+ uint64_t _seqId;
+
+public:
+ SimpleMessage(const string &str);
+ SimpleMessage(const string &str, bool hasSeqId, uint64_t seqId);
+ ~SimpleMessage();
+
+ void setValue(const string &value);
+ const string &getValue() const;
+ int getHash() const;
+ const string & getProtocol() const;
+ uint32_t getType() const;
+ bool hasSequenceId() const;
+ uint64_t getSequenceId() const;
+ uint32_t getApproxSize() const;
+
+ uint8_t priority() const { return 8; }
+
+ string toString() const { return _value; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/simpleprotocol.cpp b/messagebus/src/vespa/messagebus/testlib/simpleprotocol.cpp
new file mode 100644
index 00000000000..07a4321f05b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/simpleprotocol.cpp
@@ -0,0 +1,155 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".simpleprotocol");
+
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/messagebus/routing/routingnodeiterator.h>
+#include "simplemessage.h"
+#include "simpleprotocol.h"
+#include "simplereply.h"
+
+namespace mbus {
+
+const string SimpleProtocol::NAME("Simple");
+const uint32_t SimpleProtocol::MESSAGE(1);
+const uint32_t SimpleProtocol::REPLY(2);
+
+class AllPolicy : public IRoutingPolicy {
+public:
+ void select(RoutingContext &ctx) {
+ std::vector<Route> recipients;
+ ctx.getMatchedRecipients(recipients);
+ ctx.addChildren(recipients);
+ }
+
+ void merge(RoutingContext &ctx) {
+ SimpleProtocol::simpleMerge(ctx);
+ }
+};
+
+class AllPolicyFactory : public SimpleProtocol::IPolicyFactory {
+public:
+ IRoutingPolicy::UP create(const string &) {
+ return IRoutingPolicy::UP(new AllPolicy());
+ }
+};
+
+class HashPolicy : public IRoutingPolicy {
+public:
+ void select(RoutingContext &ctx) {
+ std::vector<Route> recipients;
+ ctx.getMatchedRecipients(recipients);
+ if (!recipients.empty()) {
+ int i = static_cast<const SimpleMessage&>(ctx.getMessage()).getHash();
+ ctx.addChild(recipients[std::abs(i) % recipients.size()]);
+ }
+ }
+
+ void merge(RoutingContext &ctx) {
+ SimpleProtocol::simpleMerge(ctx);
+ }
+};
+
+class HashPolicyFactory : public SimpleProtocol::IPolicyFactory {
+public:
+ IRoutingPolicy::UP create(const string &) {
+ return IRoutingPolicy::UP(new HashPolicy());
+ }
+};
+
+SimpleProtocol::SimpleProtocol() :
+ _policies()
+{
+ addPolicyFactory("All", IPolicyFactory::SP(new AllPolicyFactory));
+ addPolicyFactory("Hash", IPolicyFactory::SP(new HashPolicyFactory));
+}
+
+SimpleProtocol::~SimpleProtocol()
+{
+ // empty
+}
+
+void
+SimpleProtocol::addPolicyFactory(const string &name,
+ IPolicyFactory::SP factory)
+{
+ _policies.insert(FactoryMap::value_type(name, factory));
+}
+
+const string &
+SimpleProtocol::getName() const
+{
+ return NAME;
+}
+
+IRoutingPolicy::UP
+SimpleProtocol::createPolicy(const string &name,
+ const string &param) const
+{
+ FactoryMap::const_iterator it = _policies.find(name);
+ if (it != _policies.end()) {
+ return it->second->create(param);
+ }
+ return IRoutingPolicy::UP();
+}
+
+Blob
+SimpleProtocol::encode(const vespalib::Version &version, const Routable &routable) const
+{
+ (void)version;
+ if (routable.getType() == MESSAGE) {
+ string str = "M";
+ str.append(static_cast<const SimpleMessage&>(routable).getValue());
+ Blob ret(str.size());
+ memcpy(ret.data(), str.data(), str.size());
+ return ret;
+ } else if (routable.getType() == REPLY) {
+ string str = "R";
+ str.append(static_cast<const SimpleReply&>(routable).getValue());
+ Blob ret(str.size());
+ memcpy(ret.data(), str.data(), str.size());
+ return ret;
+ } else {
+ return Blob(0);
+ }
+}
+
+Routable::UP
+SimpleProtocol::decode(const vespalib::Version &version, BlobRef data) const
+{
+ (void)version;
+
+ const char *d = data.data();
+ uint32_t s = data.size();
+ if (s < 1) {
+ return Routable::UP(); // too short
+ }
+ string str(d + 1, s - 1);
+ if (*d == 'M') {
+ return Routable::UP(new SimpleMessage(str));
+ } else if (*d == 'R') {
+ return Routable::UP(new SimpleReply(str));
+ } else {
+ return Routable::UP(); // unknown type
+ }
+}
+
+void
+SimpleProtocol::simpleMerge(RoutingContext &ctx)
+{
+ Reply::UP ret(new EmptyReply());
+ for (RoutingNodeIterator it = ctx.getChildIterator();
+ it.isValid(); it.next())
+ {
+ const Reply &reply = it.getReplyRef();
+ for (uint32_t i = 0; i < reply.getNumErrors(); ++i) {
+ ret->addError(reply.getError(i));
+ }
+ }
+ ctx.setReply(std::move(ret));
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/simpleprotocol.h b/messagebus/src/vespa/messagebus/testlib/simpleprotocol.h
new file mode 100644
index 00000000000..4a57d86f4a7
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/simpleprotocol.h
@@ -0,0 +1,91 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <boost/utility.hpp>
+#include <map>
+#include <string>
+#include <vespa/messagebus/iprotocol.h>
+
+namespace mbus {
+
+class SimpleProtocol : public boost::noncopyable, public IProtocol {
+public:
+ /**
+ * Defines a policy factory interface that tests can use to register arbitrary policies with this protocol.
+ */
+ class IPolicyFactory {
+ public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::shared_ptr<IPolicyFactory> SP;
+
+ /**
+ * Required for inheritance.
+ */
+ virtual ~IPolicyFactory() { }
+
+ /**
+ * Creates a new isntance of the routing policy that this factory encapsulates.
+ *
+ * @param param The param for the policy constructor.
+ * @return The routing policy created.
+ */
+ virtual IRoutingPolicy::UP create(const string &param) = 0;
+ };
+
+private:
+ typedef std::map<string, IPolicyFactory::SP> FactoryMap;
+ FactoryMap _policies;
+
+public:
+ static const string NAME;
+ static const uint32_t MESSAGE;
+ static const uint32_t REPLY;
+
+ /**
+ * Constructs a new simple protocol. This registers policy factories for both {@link SimpleAllPolicy} and
+ * {@link SimpleHashPolicy}.
+ */
+ SimpleProtocol();
+
+ /**
+ * Frees up any allocated resources.
+ */
+ virtual ~SimpleProtocol();
+
+ /**
+ * Registers a policy factory with this protocol under a given name. Whenever a policy is requested that
+ * matches this name, the factory is invoked.
+ *
+ * @param name The name of the policy.
+ * @param factory The policy factory.
+ */
+ void addPolicyFactory(const string &name,
+ IPolicyFactory::SP factory);
+
+ /**
+ * Common merge logic that can be used for any simple policy. It all errors across all replies into
+ * a new {@link EmptyReply}.
+ *
+ * @param ctx The routing context whose children to merge.
+ */
+ static void simpleMerge(RoutingContext &ctx);
+
+ // Implements IProtocol.
+ const string & getName() const;
+
+ // Implements IProtocol.
+ IRoutingPolicy::UP createPolicy(const string &name,
+ const string &param) const;
+
+ // Implements IProtocol.
+ Blob encode(const vespalib::Version &version, const Routable &routable) const;
+
+ // Implements IProtocol.
+ Routable::UP decode(const vespalib::Version &version, BlobRef data) const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/simplereply.cpp b/messagebus/src/vespa/messagebus/testlib/simplereply.cpp
new file mode 100644
index 00000000000..1559a157860
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/simplereply.cpp
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".simplereply");
+#include "simplereply.h"
+#include "simpleprotocol.h"
+
+namespace mbus {
+
+SimpleReply::SimpleReply(const string &str) :
+ Reply(),
+ _value(str)
+{
+ // empty
+}
+
+SimpleReply::~SimpleReply()
+{
+ // empty
+}
+
+void
+SimpleReply::setValue(const string &value)
+{
+ _value = value;
+}
+
+const string &
+SimpleReply::getValue() const
+{
+ return _value;
+}
+
+const string &
+SimpleReply::getProtocol() const
+{
+ return SimpleProtocol::NAME;
+}
+
+uint32_t
+SimpleReply::getType() const
+{
+ return SimpleProtocol::REPLY;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/simplereply.h b/messagebus/src/vespa/messagebus/testlib/simplereply.h
new file mode 100644
index 00000000000..6a33855976c
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/simplereply.h
@@ -0,0 +1,29 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string>
+#include <vespa/messagebus/reply.h>
+#include "simplemessage.h"
+
+namespace mbus {
+
+class SimpleReply : public Reply
+{
+private:
+ string _value;
+ SimpleReply &operator=(const SimpleReply &);
+public:
+ typedef std::unique_ptr<SimpleReply> UP;
+ SimpleReply(const string &str);
+ virtual ~SimpleReply();
+ void setValue(const string &value);
+ const string &getValue() const;
+ virtual const string & getProtocol() const;
+ virtual uint32_t getType() const;
+
+ uint8_t priority() const { return 8; }
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/slobrok.cpp b/messagebus/src/vespa/messagebus/testlib/slobrok.cpp
new file mode 100644
index 00000000000..924391c0f8d
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/slobrok.cpp
@@ -0,0 +1,107 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".slobrok");
+#include "slobrok.h"
+#include <vespa/slobrok/cfg.h>
+#include <vespa/slobrok/server/sbenv.h>
+#include <vespa/vespalib/util/sync.h>
+#include <vespa/vespalib/util/vstringfmt.h>
+
+namespace {
+class WaitTask : public FNET_Task
+{
+private:
+ bool _done;
+ vespalib::Monitor _mon;
+public:
+ WaitTask(FNET_Scheduler *s) : FNET_Task(s), _done(false), _mon() {}
+ void wait() {
+ vespalib::MonitorGuard guard(_mon);
+ while (!_done) {
+ guard.wait();
+ }
+ }
+ virtual void PerformTask() {
+ vespalib::MonitorGuard guard(_mon);
+ _done = true;
+ guard.signal();
+ }
+};
+} // namespace <unnamed>
+
+namespace mbus {
+
+void
+Slobrok::Thread::setEnv(slobrok::SBEnv *env)
+{
+ _env = env;
+}
+
+void
+Slobrok::Thread::Run(FastOS_ThreadInterface *, void *)
+{
+ if (_env->MainLoop() != 0) {
+ LOG_ABORT("Slobrok main failed");
+ }
+}
+
+void
+Slobrok::init()
+{
+ slobrok::ConfigShim shim(_port);
+ _env.reset(new slobrok::SBEnv(shim));
+ _thread.setEnv(_env.get());
+ WaitTask wt(_env->getTransport()->GetScheduler());
+ wt.ScheduleNow();
+ if (_pool.NewThread(&_thread, 0) == 0) {
+ LOG_ABORT("Could not spawn thread");
+ }
+ wt.wait();
+ int p = _env->getSupervisor()->GetListenPort();
+ LOG_ASSERT(p != 0 && (p == _port || _port == 0));
+ _port = p;
+}
+
+Slobrok::Slobrok()
+ : _pool(128000, 0),
+ _env(),
+ _port(0),
+ _thread()
+{
+ init();
+}
+
+Slobrok::Slobrok(int p)
+ : _pool(128000, 0),
+ _env(),
+ _port(p),
+ _thread()
+{
+ init();
+}
+
+Slobrok::~Slobrok()
+{
+ _env->getTransport()->ShutDown(true);
+ _pool.Close();
+}
+
+int
+Slobrok::port() const
+{
+ return _port;
+}
+
+config::ConfigUri
+Slobrok::config() const
+{
+ cloud::config::SlobroksConfigBuilder builder;
+ cloud::config::SlobroksConfig::Slobrok sb;
+ sb.connectionspec = vespalib::make_string("tcp/localhost:%d", port());
+ builder.slobrok.push_back(sb);
+ return config::ConfigUri::createFromInstance(builder);
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/slobrok.h b/messagebus/src/vespa/messagebus/testlib/slobrok.h
new file mode 100644
index 00000000000..28053e7b506
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/slobrok.h
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include <vespa/messagebus/common.h>
+#include <vespa/slobrok/cfg.h>
+
+namespace slobrok {
+class SBEnv;
+} // namespace slobrok
+
+namespace mbus {
+
+class Slobrok
+{
+private:
+ class Thread : public FastOS_Runnable {
+ private:
+ slobrok::SBEnv *_env;
+ public:
+ void setEnv(slobrok::SBEnv *env);
+ void Run(FastOS_ThreadInterface *, void *);
+ };
+ FastOS_ThreadPool _pool;
+ std::unique_ptr<slobrok::SBEnv> _env;
+ int _port;
+ Thread _thread;
+
+ Slobrok(const Slobrok &);
+ Slobrok &operator=(const Slobrok &);
+
+ void init();
+
+public:
+ typedef std::unique_ptr<Slobrok> UP;
+ Slobrok();
+ Slobrok(int port);
+ ~Slobrok();
+ int port() const;
+ config::ConfigUri config() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/slobrokstate.cpp b/messagebus/src/vespa/messagebus/testlib/slobrokstate.cpp
new file mode 100644
index 00000000000..4f825f8dba1
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/slobrokstate.cpp
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".slobrokstate");
+#include "slobrokstate.h"
+
+namespace mbus {
+
+SlobrokState::SlobrokState()
+ : _data()
+{
+}
+
+SlobrokState &
+SlobrokState::add(const string &pattern, uint32_t cnt)
+{
+ _data.push_back(std::make_pair(pattern, cnt));
+ return *this;
+}
+
+SlobrokState::ITR
+SlobrokState::begin() const
+{
+ return _data.begin();
+}
+
+SlobrokState::ITR
+SlobrokState::end() const
+{
+ return _data.end();
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/slobrokstate.h b/messagebus/src/vespa/messagebus/testlib/slobrokstate.h
new file mode 100644
index 00000000000..f8af060e4e6
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/slobrokstate.h
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vector>
+#include <vespa/messagebus/common.h>
+
+namespace mbus {
+
+class SlobrokState
+{
+public:
+ typedef std::vector<std::pair<string, uint32_t> > TYPE;
+ typedef TYPE::const_iterator ITR;
+
+private:
+ TYPE _data;
+
+public:
+ SlobrokState();
+ SlobrokState &add(const string &pattern, uint32_t cnt = 1);
+ ITR begin() const;
+ ITR end() const;
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/testlib/testserver.cpp b/messagebus/src/vespa/messagebus/testlib/testserver.cpp
new file mode 100644
index 00000000000..c0ef5f9fbf9
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/testserver.cpp
@@ -0,0 +1,106 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".testserver");
+
+#include <vespa/vespalib/util/vstringfmt.h>
+#include <vespa/messagebus/network/rpcnetworkparams.h>
+#include <vespa/messagebus/vtag.h>
+#include "oosstate.h"
+#include "simpleprotocol.h"
+#include "slobrok.h"
+#include "slobrokstate.h"
+#include "testserver.h"
+
+namespace mbus {
+
+VersionedRPCNetwork::VersionedRPCNetwork(const RPCNetworkParams &params) :
+ RPCNetwork(params),
+ _version(Vtag::currentVersion)
+{
+ // empty
+}
+
+void
+VersionedRPCNetwork::setVersion(const vespalib::Version &version)
+{
+ _version = version;
+ flushTargetPool();
+}
+
+TestServer::TestServer(const Identity &ident,
+ const RoutingSpec &spec,
+ const Slobrok &slobrok,
+ const string &oosServerPattern,
+ IProtocol::SP protocol) :
+ net(RPCNetworkParams()
+ .setIdentity(ident)
+ .setSlobrokConfig(slobrok.config())
+ .setOOSServerPattern(oosServerPattern)),
+ mb(net, ProtocolSet().add(IProtocol::SP(new SimpleProtocol())).add(protocol))
+{
+ mb.setupRouting(spec);
+}
+
+TestServer::TestServer(const MessageBusParams &mbusParams,
+ const RPCNetworkParams &netParams) :
+ net(netParams),
+ mb(net, mbusParams)
+{
+ // empty
+}
+
+bool
+TestServer::waitSlobrok(const string &pattern, uint32_t cnt)
+{
+ return waitState(SlobrokState().add(pattern, cnt));
+}
+
+bool
+TestServer::waitOOS(const string &service)
+{
+ return waitState(OOSState().add(service, true));
+}
+
+bool
+TestServer::waitState(const SlobrokState &slobrokState)
+{
+ for (uint32_t i = 0; i < 12000; ++i) {
+ bool done = true;
+ for (SlobrokState::ITR itr = slobrokState.begin();
+ itr != slobrokState.end(); ++itr)
+ {
+ slobrok::api::MirrorAPI::SpecList res = net.getMirror().lookup(itr->first);
+ if (res.size() != itr->second) {
+ done = false;
+ }
+ }
+ if (done) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ return false;
+}
+
+bool
+TestServer::waitState(const OOSState &oosState)
+{
+ for (uint32_t i = 0; i < 12000; ++i) {
+ bool done = true;
+ for (OOSState::ITR itr = oosState.begin();
+ itr != oosState.end(); ++itr)
+ {
+ if (net.getOOSManager().isOOS(itr->first) != itr->second) {
+ done = false;
+ }
+ }
+ if (done) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ return false;
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/testlib/testserver.h b/messagebus/src/vespa/messagebus/testlib/testserver.h
new file mode 100644
index 00000000000..ed0720eef1a
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/testlib/testserver.h
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <boost/utility.hpp>
+#include <memory>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/network/rpcnetwork.h>
+
+namespace mbus {
+
+class Identity;
+class RoutingTableSpec;
+class Slobrok;
+class SlobrokState;
+class OOSState;
+
+class VersionedRPCNetwork : public RPCNetwork {
+private:
+ vespalib::Version _version;
+
+protected:
+ const vespalib::Version &getVersion() const { return _version; }
+
+public:
+ VersionedRPCNetwork(const RPCNetworkParams &params);
+ void setVersion(const vespalib::Version &version);
+};
+
+class TestServer : public boost::noncopyable {
+public:
+ typedef std::unique_ptr<TestServer> UP;
+
+ VersionedRPCNetwork net;
+ MessageBus mb;
+
+ TestServer(const Identity &ident,
+ const RoutingSpec &spec,
+ const Slobrok &slobrok,
+ const string &oosServerPattern = "",
+ IProtocol::SP protocol = IProtocol::SP());
+ TestServer(const MessageBusParams &mbusParams,
+ const RPCNetworkParams &netParams);
+
+ bool waitSlobrok(const string &pattern, uint32_t cnt = 1);
+ bool waitOOS(const string &service);
+
+ bool waitState(const SlobrokState &slobrokState);
+ bool waitState(const OOSState &oosState);
+};
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/trace.h b/messagebus/src/vespa/messagebus/trace.h
new file mode 100644
index 00000000000..98356fb7b51
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/trace.h
@@ -0,0 +1,18 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/trace/trace.h>
+#include <vespa/messagebus/tracenode.h>
+
+namespace mbus {
+
+ typedef vespalib::Trace Trace;
+
+#define MBUS_TRACE2(ttrace, level, note, addTime) \
+ VESPALIB_TRACE2(ttrace, level, note, addTime)
+
+#define MBUS_TRACE(trace, level, note) VESPALIB_TRACE2(trace, level, note, true)
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/tracelevel.h b/messagebus/src/vespa/messagebus/tracelevel.h
new file mode 100644
index 00000000000..8ec862eecab
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/tracelevel.h
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/vespalib/trace/tracelevel.h>
+namespace mbus {
+
+ typedef vespalib::TraceLevel TraceLevel;
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/tracenode.h b/messagebus/src/vespa/messagebus/tracenode.h
new file mode 100644
index 00000000000..1dddf39428b
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/tracenode.h
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/messagebus/common.h>
+#include <vespa/vespalib/trace/tracenode.h>
+
+namespace mbus {
+
+ typedef vespalib::TraceNode TraceNode;
+
+} // namespace mbus
+
diff --git a/messagebus/src/vespa/messagebus/vtag.cpp b/messagebus/src/vespa/messagebus/vtag.cpp
new file mode 100644
index 00000000000..0a42b5e6ec0
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/vtag.cpp
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <string.h>
+#include <stdio.h>
+#include "vtag.h"
+#include <vespa/vespalib/component/version.h>
+
+#ifndef V_TAG
+#define V_TAG "NOTAG"
+#define V_TAG_DATE "NOTAG"
+#define V_TAG_SYSTEM "NOTAG"
+#define V_TAG_SYSTEM_REV "NOTAG"
+#define V_TAG_BUILDER "NOTAG"
+#define V_TAG_VERSION "0"
+#define V_TAG_ARCH "NOTAG"
+#endif
+
+namespace mbus {
+
+char VersionTag[] = V_TAG;
+char VersionTagDate[] = V_TAG_DATE;
+char VersionTagSystem[] = V_TAG_SYSTEM;
+char VersionTagSystemRev[] = V_TAG_SYSTEM_REV;
+char VersionTagBuilder[] = V_TAG_BUILDER;
+char VersionTagPkg[] = V_TAG_PKG;
+char VersionTagComponent[] = V_TAG_COMPONENT;
+char VersionTagArch[] = V_TAG_ARCH;
+
+vespalib::Version Vtag::currentVersion(VersionTagComponent);
+
+void
+Vtag::printVersionNice()
+{
+ char *s = VersionTag;
+ bool needdate = true;
+ if (strncmp(VersionTag, "V_", 2) == 0) {
+ s += 2;
+ do {
+ while (strchr("0123456789", *s) != NULL) {
+ printf("%c", *s++);
+ }
+ if (strncmp(s, "_RELEASE", 8) == 0) {
+ needdate = false;
+ break;
+ }
+ if (strncmp(s, "_RC", 3) == 0) {
+ char *e = strchr(s, '-');
+ if (e == NULL) {
+ printf("%s", s);
+ } else {
+ printf("%.*s", (int)(e-s), s);
+ }
+ needdate = false;
+ break;
+ }
+ if (*s == '_' && strchr("0123456789", *++s)) {
+ printf(".");
+ } else {
+ break;
+ }
+ } while (*s && *s != '-');
+ } else {
+ char *e = strchr(s, '-');
+ if (e == NULL) {
+ printf("%s", s);
+ } else {
+ printf("%.*s", (int)(e-s), s);
+ }
+ }
+ if (needdate) {
+ s = VersionTagDate;
+ char *e = strchr(s, '-');
+ if (e == NULL) {
+ printf("-%s", s);
+ } else {
+ printf("-%.*s", (int)(e-s), s);
+ }
+ }
+}
+
+} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/vtag.h b/messagebus/src/vespa/messagebus/vtag.h
new file mode 100644
index 00000000000..505ffb42161
--- /dev/null
+++ b/messagebus/src/vespa/messagebus/vtag.h
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+namespace vespalib {
+class Version;
+}
+
+namespace mbus {
+
+extern char VersionTag[];
+extern char VersionTagDate[];
+extern char VersionTagSystem[];
+extern char VersionTagSystemRev[];
+extern char VersionTagBuilder[];
+
+class Vtag {
+public:
+ static vespalib::Version currentVersion;
+ static void printVersionNice();
+};
+
+} // namespace messagebus
+
diff --git a/messagebus/test/CMakeLists.txt b/messagebus/test/CMakeLists.txt
new file mode 100644
index 00000000000..a6d2fdcb8a0
--- /dev/null
+++ b/messagebus/test/CMakeLists.txt
@@ -0,0 +1,10 @@
+vespa_add_module_dependency(slobrok_slobrokserver)
+vespa_add_module_dependency(messagebus_messagebus-test)
+vespa_add_module_dependency(messagebus)
+add_subdirectory(src/binref)
+add_subdirectory(src/tests/compile-cpp)
+add_subdirectory(src/tests/compile-java)
+add_subdirectory(src/tests/error)
+add_subdirectory(src/tests/errorcodes)
+add_subdirectory(src/tests/speed)
+add_subdirectory(src/tests/trace)
diff --git a/messagebus/test/src/.gitignore b/messagebus/test/src/.gitignore
new file mode 100644
index 00000000000..8689bfd3624
--- /dev/null
+++ b/messagebus/test/src/.gitignore
@@ -0,0 +1,8 @@
+Makefile.inc
+Makefile.ini
+config.cfg
+config_command.sh
+configure
+project.dsw
+versiontag.mak
+/messagebus_test.mak
diff --git a/messagebus/test/src/binref/.gitignore b/messagebus/test/src/binref/.gitignore
new file mode 100644
index 00000000000..c1b83610972
--- /dev/null
+++ b/messagebus/test/src/binref/.gitignore
@@ -0,0 +1,5 @@
+.depend
+Makefile
+compilejava
+env.sh
+runjava
diff --git a/messagebus/test/src/binref/CMakeLists.txt b/messagebus/test/src/binref/CMakeLists.txt
new file mode 100644
index 00000000000..20594f98d02
--- /dev/null
+++ b/messagebus/test/src/binref/CMakeLists.txt
@@ -0,0 +1,5 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+configure_file(compilejava.in compilejava @ONLY)
+configure_file(runjava.in runjava @ONLY)
+configure_file(env.sh.in env.sh @ONLY)
diff --git a/messagebus/test/src/binref/compilejava.in b/messagebus/test/src/binref/compilejava.in
new file mode 100755
index 00000000000..bebe4374afb
--- /dev/null
+++ b/messagebus/test/src/binref/compilejava.in
@@ -0,0 +1,11 @@
+#!/bin/sh
+unset VESPA_LOG_TARGET
+CLASSPATH=@PROJECT_BINARY_DIR@/messagebus/target/messagebus-jar-with-dependencies.jar
+CLASSPATH=$CLASSPATH:@PROJECT_BINARY_DIR@/component/target/component.jar
+CLASSPATH=$CLASSPATH:.
+
+if [ $# -lt 1 ]; then
+ echo "usage: compilejava file ..."
+ exit 1
+fi
+exec javac -classpath $CLASSPATH "$@"
diff --git a/messagebus/test/src/binref/env.sh.in b/messagebus/test/src/binref/env.sh.in
new file mode 100644
index 00000000000..dda4234226f
--- /dev/null
+++ b/messagebus/test/src/binref/env.sh.in
@@ -0,0 +1,2 @@
+BINREF=@CMAKE_CURRENT_BINARY_DIR@
+export BINREF
diff --git a/messagebus/test/src/binref/progctl.sh b/messagebus/test/src/binref/progctl.sh
new file mode 120000
index 00000000000..781d2058cf9
--- /dev/null
+++ b/messagebus/test/src/binref/progctl.sh
@@ -0,0 +1 @@
+../../../../vespalib/src/vespa/vespalib/testkit/progctl.sh \ No newline at end of file
diff --git a/messagebus/test/src/binref/runjava.in b/messagebus/test/src/binref/runjava.in
new file mode 100755
index 00000000000..20d4de0a477
--- /dev/null
+++ b/messagebus/test/src/binref/runjava.in
@@ -0,0 +1,13 @@
+#!/bin/sh
+unset VESPA_LOG_TARGET
+unset LD_PRELOAD
+CLASSPATH=@PROJECT_BINARY_DIR@/messagebus/target/messagebus-jar-with-dependencies.jar
+CLASSPATH=$CLASSPATH:@PROJECT_BINARY_DIR@/component/target/component.jar
+CLASSPATH=$CLASSPATH:.
+if [ $# -lt 1 ]; then
+ echo "usage: runjava <class> [args]"
+ exit 1
+fi
+CLASS=$1
+shift
+exec java -cp $CLASSPATH $CLASS "$@"
diff --git a/messagebus/test/src/binref/sbcmd b/messagebus/test/src/binref/sbcmd
new file mode 120000
index 00000000000..49b9c735282
--- /dev/null
+++ b/messagebus/test/src/binref/sbcmd
@@ -0,0 +1 @@
+../../../../slobrok/src/apps/sbcmd/sbcmd \ No newline at end of file
diff --git a/messagebus/test/src/binref/slobrok b/messagebus/test/src/binref/slobrok
new file mode 120000
index 00000000000..5bc8ae2a9f2
--- /dev/null
+++ b/messagebus/test/src/binref/slobrok
@@ -0,0 +1 @@
+../../../../slobrok/src/apps/slobrok/slobrok \ No newline at end of file
diff --git a/messagebus/test/src/binref/testrun.sh b/messagebus/test/src/binref/testrun.sh
new file mode 120000
index 00000000000..457b9f75c5e
--- /dev/null
+++ b/messagebus/test/src/binref/testrun.sh
@@ -0,0 +1 @@
+../../../../vespalib/src/vespa/vespalib/testkit/testrun.sh \ No newline at end of file
diff --git a/messagebus/test/src/setup.sh b/messagebus/test/src/setup.sh
new file mode 100755
index 00000000000..2111dfb447c
--- /dev/null
+++ b/messagebus/test/src/setup.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -e
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+MYDIR=`pwd`
+cp ../../src/cpp/Makefile.inc .
+cp ../../src/cpp/versiontag.mak .
+cp ../../src/cpp/config.cfg .
+cp ../../src/cpp/config_command.sh .
+sh config_command.sh
+echo MODULEDEP_INCLUDES += -I$MYDIR/../../src/cpp >> Makefile.ini
+echo LIBDIR_MESSAGEBUS=$MYDIR/../../src/cpp/messagebus >> Makefile.ini
+echo LIBDIR_MESSAGEBUS-TEST=$MYDIR/../../src/cpp/messagebus/testlib >> Makefile.ini
diff --git a/messagebus/test/src/test-report-index.html b/messagebus/test/src/test-report-index.html
new file mode 100644
index 00000000000..4ddd6eab8f9
--- /dev/null
+++ b/messagebus/test/src/test-report-index.html
@@ -0,0 +1,17 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<html>
+ <title>Messagebus</title>
+ <body>
+ <h1>Messagebus Test Reports</h1>
+ <ul>
+ <li><a href="../../../messagebus/target/site/surefire-report.html">Messagebus Java Test Report</a></li>
+ <li><a href="test-report-cpp/test-report.html">Messagebus C++ Test Report</a></li>
+ <li><a href="test-report-cross/test-report.html">Messagebus Cross-language Test Report</a></li>
+ </ul>
+ <h1>Messagebus API Documentation</h1>
+ <ul>
+ <li><a href="../../../messagebus/src/java/docs/javadoc/index.html">Messagebus Java API Documentation</a></li>
+ <li><a href="../../../messagebus/src/cpp/doxygen/html/index.html">Messagebus C++ API Documentation</a></li>
+ </ul>
+ </body>
+</html>
diff --git a/messagebus/test/src/testlist.txt b/messagebus/test/src/testlist.txt
new file mode 100644
index 00000000000..3ff4369c385
--- /dev/null
+++ b/messagebus/test/src/testlist.txt
@@ -0,0 +1,6 @@
+tests/compile-java
+tests/compile-cpp
+tests/trace
+tests/error
+tests/errorcodes
+tests/speed
diff --git a/messagebus/test/src/tests/compile-cpp/.gitignore b/messagebus/test/src/tests/compile-cpp/.gitignore
new file mode 100644
index 00000000000..14e4fb37c45
--- /dev/null
+++ b/messagebus/test/src/tests/compile-cpp/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+compile-cpp_test
+messagebus_test_compile-cpp_test_app
diff --git a/messagebus/test/src/tests/compile-cpp/CMakeLists.txt b/messagebus/test/src/tests/compile-cpp/CMakeLists.txt
new file mode 100644
index 00000000000..5491399fce2
--- /dev/null
+++ b/messagebus/test/src/tests/compile-cpp/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_test_compile-cpp_test_app
+ SOURCES
+ compile-cpp.cpp
+ DEPENDS
+)
+vespa_add_test(NAME messagebus_test_compile-cpp_test_app NO_VALGRIND COMMAND messagebus_test_compile-cpp_test_app)
diff --git a/messagebus/test/src/tests/compile-cpp/DESC b/messagebus/test/src/tests/compile-cpp/DESC
new file mode 100644
index 00000000000..465d625ca9e
--- /dev/null
+++ b/messagebus/test/src/tests/compile-cpp/DESC
@@ -0,0 +1,2 @@
+simple compilation test to check dependencies.
+
diff --git a/messagebus/test/src/tests/compile-cpp/FILES b/messagebus/test/src/tests/compile-cpp/FILES
new file mode 100644
index 00000000000..956ce16a56e
--- /dev/null
+++ b/messagebus/test/src/tests/compile-cpp/FILES
@@ -0,0 +1 @@
+compile-cpp.cpp
diff --git a/messagebus/test/src/tests/compile-cpp/compile-cpp.cpp b/messagebus/test/src/tests/compile-cpp/compile-cpp.cpp
new file mode 100644
index 00000000000..e15b57859b7
--- /dev/null
+++ b/messagebus/test/src/tests/compile-cpp/compile-cpp.cpp
@@ -0,0 +1,16 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("compile-cpp_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/routing/route.h>
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("compile-cpp_test");
+ mbus::Route r;
+ TEST_DONE();
+}
diff --git a/messagebus/test/src/tests/compile-java/.gitignore b/messagebus/test/src/tests/compile-java/.gitignore
new file mode 100644
index 00000000000..d615ebbafe7
--- /dev/null
+++ b/messagebus/test/src/tests/compile-java/.gitignore
@@ -0,0 +1,4 @@
+*.class
+.depend
+Makefile
+compile-java_test
diff --git a/messagebus/test/src/tests/compile-java/CMakeLists.txt b/messagebus/test/src/tests/compile-java/CMakeLists.txt
new file mode 100644
index 00000000000..3e7665f5fed
--- /dev/null
+++ b/messagebus/test/src/tests/compile-java/CMakeLists.txt
@@ -0,0 +1,2 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_test(NAME messagebus_test_compile-java_test NO_VALGRIND COMMAND sh compile-java_test.sh)
diff --git a/messagebus/test/src/tests/compile-java/DESC b/messagebus/test/src/tests/compile-java/DESC
new file mode 100644
index 00000000000..465d625ca9e
--- /dev/null
+++ b/messagebus/test/src/tests/compile-java/DESC
@@ -0,0 +1,2 @@
+simple compilation test to check dependencies.
+
diff --git a/messagebus/test/src/tests/compile-java/FILES b/messagebus/test/src/tests/compile-java/FILES
new file mode 100644
index 00000000000..5b154bb1605
--- /dev/null
+++ b/messagebus/test/src/tests/compile-java/FILES
@@ -0,0 +1 @@
+TestCompile.java
diff --git a/messagebus/test/src/tests/compile-java/TestCompile.java b/messagebus/test/src/tests/compile-java/TestCompile.java
new file mode 100644
index 00000000000..443ae093794
--- /dev/null
+++ b/messagebus/test/src/tests/compile-java/TestCompile.java
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+import com.yahoo.messagebus.EmptyReply;
+
+public class TestCompile {
+ public static void main(String[] args) {
+ EmptyReply er = new EmptyReply();
+ }
+}
diff --git a/messagebus/test/src/tests/compile-java/compile-java_test.sh b/messagebus/test/src/tests/compile-java/compile-java_test.sh
new file mode 100755
index 00000000000..f3da918eae1
--- /dev/null
+++ b/messagebus/test/src/tests/compile-java/compile-java_test.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+. ../../binref/env.sh
+
+$BINREF/compilejava TestCompile.java
+$BINREF/runjava TestCompile
+
diff --git a/messagebus/test/src/tests/create-test.sh b/messagebus/test/src/tests/create-test.sh
new file mode 100755
index 00000000000..27277838a07
--- /dev/null
+++ b/messagebus/test/src/tests/create-test.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+gen_ignore_file() {
+ echo "generating '$1' ..."
+ echo ".depend" > $1
+ echo "Makefile" >> $1
+ echo "${test}_test" >> $1
+}
+
+gen_project_file() {
+ echo "generating '$1' ..."
+ echo "APPLICATION ${test}_test" > $1
+ echo "OBJS $test" >> $1
+ echo "EXTERNALLIBS messagebus messagebus-test" >> $1
+ echo "EXTERNALLIBS slobrokserver slobrok fnet vespalib config vespalog" >> $1
+ echo "" >> $1
+ echo "CUSTOMMAKE" >> $1
+ echo "test: depend ${test}_test" >> $1
+ echo -e "\t@./${test}_test" >> $1
+}
+
+gen_source() {
+ echo "generating '$1' ..."
+ echo "#include <vespa/log/log.h>" > $1
+ echo "LOG_SETUP(\"${test}_test\");" >> $1
+ echo "#include <vespa/fastos/fastos.h>" >> $1
+ echo "#include <vespa/vespalib/testkit/testapp.h>" >> $1
+ echo "" >> $1
+ echo "TEST_SETUP(Test);" >> $1
+ echo "" >> $1
+ echo "int" >> $1
+ echo "Test::Main()" >> $1
+ echo "{" >> $1
+ echo " TEST_INIT(\"${test}_test\");" >> $1
+ echo " TEST_DONE();" >> $1
+ echo "}" >> $1
+}
+
+gen_desc() {
+ echo "generating '$1' ..."
+ echo "$test test. Take a look at $test.cpp for details." > $1
+}
+
+gen_file_list() {
+ echo "generating '$1' ..."
+ echo "$test.cpp" > $1
+}
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <name>"
+ echo " name: name of the test to create"
+ exit 1
+fi
+
+test=$1
+if [ -e $test ]; then
+ echo "$test already present, don't want to mess it up..."
+ exit 1
+fi
+
+echo "creating directory '$test' ..."
+mkdir -p $test || exit 1
+cd $test || exit 1
+test=`basename $test`
+
+gen_ignore_file .cvsignore
+gen_project_file fastos.project
+gen_source $test.cpp
+gen_desc DESC
+gen_file_list FILES
diff --git a/messagebus/test/src/tests/error/.gitignore b/messagebus/test/src/tests/error/.gitignore
new file mode 100644
index 00000000000..20cb631e9e8
--- /dev/null
+++ b/messagebus/test/src/tests/error/.gitignore
@@ -0,0 +1,15 @@
+*.class
+.depend
+Makefile
+cpp-client
+cpp-server
+error_test
+out.*
+pid.*
+routing.cfg
+slobrok.cfg
+/cpp-client-error
+/cpp-server-error
+messagebus_test_error_test_app
+messagebus_test_cpp-client-error_app
+messagebus_test_cpp-server-error_app
diff --git a/messagebus/test/src/tests/error/CMakeLists.txt b/messagebus/test/src/tests/error/CMakeLists.txt
new file mode 100644
index 00000000000..abd089571f5
--- /dev/null
+++ b/messagebus/test/src/tests/error/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_test_error_test_app
+ SOURCES
+ error.cpp
+ DEPENDS
+)
+vespa_add_test(NAME messagebus_test_error_test_app NO_VALGRIND COMMAND sh error_test.sh)
+vespa_add_executable(messagebus_test_cpp-server-error_app
+ SOURCES
+ cpp-server.cpp
+ DEPENDS
+)
+vespa_add_executable(messagebus_test_cpp-client-error_app
+ SOURCES
+ cpp-client.cpp
+ DEPENDS
+)
diff --git a/messagebus/test/src/tests/error/DESC b/messagebus/test/src/tests/error/DESC
new file mode 100644
index 00000000000..171966761ee
--- /dev/null
+++ b/messagebus/test/src/tests/error/DESC
@@ -0,0 +1,2 @@
+Check that java and cpp messagebus components are able to pass errors
+to each other and preserve meaning.
diff --git a/messagebus/test/src/tests/error/FILES b/messagebus/test/src/tests/error/FILES
new file mode 100644
index 00000000000..571002a917f
--- /dev/null
+++ b/messagebus/test/src/tests/error/FILES
@@ -0,0 +1,8 @@
+error.cpp
+out.server.cpp
+out.server.java
+cpp-client.cpp
+cpp-server.cpp
+JavaClient.java
+JavaServer.java
+routing-template.cfg
diff --git a/messagebus/test/src/tests/error/JavaClient.java b/messagebus/test/src/tests/error/JavaClient.java
new file mode 100644
index 00000000000..e263b3597da
--- /dev/null
+++ b/messagebus/test/src/tests/error/JavaClient.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.test.*;
+import com.yahoo.config.*;
+import com.yahoo.messagebus.routing.*;
+import com.yahoo.messagebus.network.*;
+import com.yahoo.messagebus.network.rpc.*;
+import com.yahoo.messagebus.network.rpc.test.*;
+import java.util.Arrays;
+import java.util.logging.*;
+
+public class JavaClient {
+
+ private static Logger log = Logger.getLogger(JavaClient.class.getName());
+
+ public static void main(String[] args) {
+ try {
+ RPCMessageBus mb = new RPCMessageBus(
+ Arrays.asList((Protocol)new SimpleProtocol()),
+ new RPCNetworkParams()
+ .setIdentity(new Identity("server/java"))
+ .setSlobrokConfigId("file:slobrok.cfg"),
+ "file:routing.cfg");
+
+ Receptor src = new Receptor();
+ Message msg = null;
+ Reply reply = null;
+
+ SourceSession session = mb.getMessageBus().createSourceSession(src, new SourceSessionParams().setTimeout(300));
+ for (int i = 0; i < 10; i++) {
+ msg = new SimpleMessage("test");
+ msg.getTrace().setLevel(9);
+ session.send(msg, "test");
+ reply = src.getReply(60);
+ if (reply == null) {
+ System.err.println("JAVA-CLIENT: no reply");
+ } else {
+ System.err.println("JAVA-CLIENT:\n" + reply.getTrace());
+ if (reply.getNumErrors() == 2) {
+ break;
+ }
+ }
+ Thread.sleep(1000);
+ }
+ if (reply == null) {
+ System.err.println("JAVA-CLIENT: no reply");
+ System.exit(1);
+ }
+ if (reply.getNumErrors() != 2 ||
+ reply.getError(0).getCode() != ErrorCode.APP_FATAL_ERROR + 1 ||
+ reply.getError(1).getCode() != ErrorCode.APP_FATAL_ERROR + 2 ||
+ !reply.getError(0).getMessage().equals("ERR 1") ||
+ !reply.getError(1).getMessage().equals("ERR 2"))
+ {
+ System.err.printf("JAVA-CLIENT: wrong errors\n");
+ System.exit(1);
+ }
+ session.destroy();
+ mb.destroy();
+ } catch (Exception e) {
+ log.log(Level.SEVERE, "JAVA-CLIENT: Failed", e);
+ System.exit(1);
+ }
+ }
+}
diff --git a/messagebus/test/src/tests/error/JavaServer.java b/messagebus/test/src/tests/error/JavaServer.java
new file mode 100644
index 00000000000..b5321f41fc3
--- /dev/null
+++ b/messagebus/test/src/tests/error/JavaServer.java
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.test.*;
+import com.yahoo.config.*;
+import com.yahoo.messagebus.routing.*;
+import com.yahoo.messagebus.network.*;
+import com.yahoo.messagebus.network.rpc.*;
+import java.util.Arrays;
+import java.util.logging.*;
+
+public class JavaServer implements MessageHandler {
+
+ private static Logger log = Logger.getLogger(JavaServer.class.getName());
+
+ private DestinationSession session;
+
+ public JavaServer(RPCMessageBus mb) {
+ session = mb.getMessageBus().createDestinationSession("session", true, this);
+ }
+
+ public void handleMessage(Message msg) {
+ Reply reply = new EmptyReply();
+ msg.swapState(reply);
+ reply.addError(new com.yahoo.messagebus.Error(ErrorCode.APP_FATAL_ERROR + 1, "ERR 1"));
+ reply.addError(new com.yahoo.messagebus.Error(ErrorCode.APP_FATAL_ERROR + 2, "ERR 2"));
+ session.reply(reply);
+ }
+
+ public static void main(String[] args) {
+ try {
+ RPCMessageBus mb = new RPCMessageBus(
+ Arrays.asList((Protocol)new SimpleProtocol()),
+ new RPCNetworkParams()
+ .setIdentity(new Identity("server/java"))
+ .setSlobrokConfigId("file:slobrok.cfg"),
+ "file:routing.cfg");
+ JavaServer server = new JavaServer(mb);
+ System.out.println("java server started");
+ while (true) {
+ Thread.sleep(1000);
+ }
+ } catch (Exception e) {
+ log.log(Level.SEVERE, "JAVA-SERVER: Failed", e);
+ System.exit(1);
+ }
+ }
+}
diff --git a/messagebus/test/src/tests/error/cpp-client.cpp b/messagebus/test/src/tests/error/cpp-client.cpp
new file mode 100644
index 00000000000..4f94a13977c
--- /dev/null
+++ b/messagebus/test/src/tests/error/cpp-client.cpp
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("cpp-client");
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/rpcmessagebus.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/iprotocol.h>
+#include <vespa/messagebus/protocolset.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/vespalib/util/sync.h>
+
+using namespace mbus;
+
+class App : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+int
+App::Main()
+{
+ RPCMessageBus mb(ProtocolSet().add(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("server/cpp"))
+ .setSlobrokConfig("file:slobrok.cfg"),
+ "file:routing.cfg");
+
+ Receptor src;
+ Message::UP msg;
+ Reply::UP reply;
+
+ SourceSession::UP ss = mb.getMessageBus().createSourceSession(src, SourceSessionParams().setTimeout(300));
+ for (int i = 0; i < 10; ++i) {
+ msg.reset(new SimpleMessage("test"));
+ msg->getTrace().setLevel(9);
+ ss->send(std::move(msg), "test");
+ reply = src.getReply(600); // 10 minutes timeout
+ if (reply.get() == 0) {
+ fprintf(stderr, "CPP-CLIENT: no reply\n");
+ } else {
+ fprintf(stderr, "CPP-CLIENT:\n%s\n",
+ reply->getTrace().toString().c_str());
+ if (reply->getNumErrors() == 2) {
+ break;
+ }
+ }
+ FastOS_Thread::Sleep(1000);
+ }
+ if (reply.get() == 0) {
+ fprintf(stderr, "CPP-CLIENT: no reply\n");
+ return 1;
+ }
+ if (reply->getNumErrors() != 2 ||
+ reply->getError(0).getCode() != (ErrorCode::APP_FATAL_ERROR + 1) ||
+ reply->getError(1).getCode() != (ErrorCode::APP_FATAL_ERROR + 2) ||
+ reply->getError(0).getMessage() != "ERR 1" ||
+ reply->getError(1).getMessage() != "ERR 2")
+ {
+ fprintf(stderr, "CPP-CLIENT: wrong errors\n");
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ App app;
+ return app.Entry(argc, argv);
+}
diff --git a/messagebus/test/src/tests/error/cpp-server.cpp b/messagebus/test/src/tests/error/cpp-server.cpp
new file mode 100644
index 00000000000..2eb929f6ca9
--- /dev/null
+++ b/messagebus/test/src/tests/error/cpp-server.cpp
@@ -0,0 +1,73 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("cpp-server");
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/rpcmessagebus.h>
+#include <vespa/messagebus/iprotocol.h>
+#include <vespa/messagebus/protocolset.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/error.h>
+#include <vespa/messagebus/errorcode.h>
+
+using namespace mbus;
+
+class Server : public IMessageHandler
+{
+private:
+ DestinationSession::UP _session;
+public:
+ Server(MessageBus &bus);
+ ~Server();
+ void handleMessage(Message::UP msg);
+};
+
+Server::Server(MessageBus &bus)
+ : _session(bus.createDestinationSession("session", true, *this))
+{
+ fprintf(stderr, "cpp server started\n");
+}
+
+Server::~Server()
+{
+ _session.reset();
+}
+
+void
+Server::handleMessage(Message::UP msg) {
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR + 1, "ERR 1"));
+ reply->addError(Error(ErrorCode::APP_FATAL_ERROR + 2, "ERR 2"));
+ _session->reply(std::move(reply));
+}
+
+class App : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+int
+App::Main()
+{
+ RPCMessageBus mb(ProtocolSet().add(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("server/cpp"))
+ .setSlobrokConfig("file:slobrok.cfg"),
+ "file:routing.cfg");
+ Server server(mb.getMessageBus());
+ while (true) {
+ FastOS_Thread::Sleep(1000);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ App app;
+ return app.Entry(argc, argv);
+}
diff --git a/messagebus/test/src/tests/error/ctl.sh b/messagebus/test/src/tests/error/ctl.sh
new file mode 100755
index 00000000000..ea969749808
--- /dev/null
+++ b/messagebus/test/src/tests/error/ctl.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+sh ../../binref/progctl.sh progdefs.sh "$@"
diff --git a/messagebus/test/src/tests/error/error.cpp b/messagebus/test/src/tests/error/error.cpp
new file mode 100644
index 00000000000..9b01e5d61d0
--- /dev/null
+++ b/messagebus/test/src/tests/error/error.cpp
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("error_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
+using namespace mbus;
+using vespalib::make_string;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("error_test");
+ Slobrok slobrok;
+ { // Make slobrok config
+ EXPECT_TRUE(system("echo slobrok[1] > slobrok.cfg") == 0);
+ EXPECT_TRUE(system(make_string("echo 'slobrok[0].connectionspec tcp/localhost:%d' "
+ ">> slobrok.cfg", slobrok.port()).c_str()) == 0);
+ }
+ { // CPP SERVER
+ { // Make routing config
+ EXPECT_TRUE(system("cat routing-template.cfg | sed 's#session#cpp/session#' > routing.cfg") == 0);
+ }
+ fprintf(stderr, "STARTING CPP-SERVER\n");
+ EXPECT_TRUE(system("sh ctl.sh start server cpp") == 0);
+ EXPECT_TRUE(system("./messagebus_test_cpp-client-error_app") == 0);
+ EXPECT_TRUE(system("../../binref/runjava JavaClient") == 0);
+ EXPECT_TRUE(system("sh ctl.sh stop server cpp") == 0);
+ }
+ { // JAVA SERVER
+ { // Make routing config
+ EXPECT_TRUE(system("cat routing-template.cfg | sed 's#session#java/session#' > routing.cfg") == 0);
+ }
+ fprintf(stderr, "STARTING JAVA-SERVER\n");
+ EXPECT_TRUE(system("sh ctl.sh start server java") == 0);
+ EXPECT_TRUE(system("./messagebus_test_cpp-client-error_app") == 0);
+ EXPECT_TRUE(system("../../binref/runjava JavaClient") == 0);
+ EXPECT_TRUE(system("sh ctl.sh stop server java") == 0);
+ }
+ TEST_DONE();
+}
diff --git a/messagebus/test/src/tests/error/error_test.sh b/messagebus/test/src/tests/error/error_test.sh
new file mode 100755
index 00000000000..bd9ea35643d
--- /dev/null
+++ b/messagebus/test/src/tests/error/error_test.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+. ../../binref/env.sh
+
+$BINREF/compilejava JavaServer.java
+$BINREF/compilejava JavaClient.java
+VESPA_LOG_LEVEL='all -spam' ./messagebus_test_error_test_app
diff --git a/messagebus/test/src/tests/error/progdefs.sh b/messagebus/test/src/tests/error/progdefs.sh
new file mode 100644
index 00000000000..2f6f37a9425
--- /dev/null
+++ b/messagebus/test/src/tests/error/progdefs.sh
@@ -0,0 +1,3 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+prog server cpp "" "./messagebus_test_cpp-server-error_app"
+prog server java "" "../../binref/runjava JavaServer"
diff --git a/messagebus/test/src/tests/error/routing-template.cfg b/messagebus/test/src/tests/error/routing-template.cfg
new file mode 100644
index 00000000000..4b938c9cc82
--- /dev/null
+++ b/messagebus/test/src/tests/error/routing-template.cfg
@@ -0,0 +1,11 @@
+routingtable[1]
+routingtable[0].protocol "Simple"
+routingtable[0].hop[1]
+routingtable[0].hop[0].name "server"
+routingtable[0].hop[0].selector "server/session"
+routingtable[0].hop[0].recipient[1]
+routingtable[0].hop[0].recipient[0] "server/session"
+routingtable[0].route[1]
+routingtable[0].route[0].name "test"
+routingtable[0].route[0].hop[1]
+routingtable[0].route[0].hop[0] "server"
diff --git a/messagebus/test/src/tests/errorcodes/.gitignore b/messagebus/test/src/tests/errorcodes/.gitignore
new file mode 100644
index 00000000000..13957172a38
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/.gitignore
@@ -0,0 +1,7 @@
+.depend
+DumpCodes.class
+Makefile
+cpp-dump.txt
+dumpcodes
+java-dump.txt
+messagebus_test_dumpcodes_app
diff --git a/messagebus/test/src/tests/errorcodes/CMakeLists.txt b/messagebus/test/src/tests/errorcodes/CMakeLists.txt
new file mode 100644
index 00000000000..3f08783b363
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_test_dumpcodes_app
+ SOURCES
+ dumpcodes.cpp
+ DEPENDS
+)
+vespa_add_test(NAME messagebus_test_dumpcodes_app NO_VALGRIND COMMAND sh errorcodes_test.sh)
diff --git a/messagebus/test/src/tests/errorcodes/DESC b/messagebus/test/src/tests/errorcodes/DESC
new file mode 100644
index 00000000000..103ebb4698f
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/DESC
@@ -0,0 +1,2 @@
+A small test to check that error codes are equal in the Java and C++
+implementations.
diff --git a/messagebus/test/src/tests/errorcodes/DumpCodes.java b/messagebus/test/src/tests/errorcodes/DumpCodes.java
new file mode 100644
index 00000000000..8eb97813404
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/DumpCodes.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+import com.yahoo.messagebus.ErrorCode;
+
+public class DumpCodes {
+
+ private static void dump(String desc, int value) {
+ String name = ErrorCode.getName(value);
+ System.out.printf("%s => %d => \"%s\"\n", desc, value,
+ name != null ? name : "");
+ }
+
+ public static void main(String[] args) {
+ dump("NONE", ErrorCode.NONE);
+
+ dump("SEND_QUEUE_FULL", ErrorCode.SEND_QUEUE_FULL);
+ dump("NO_ADDRESS_FOR_SERVICE", ErrorCode.NO_ADDRESS_FOR_SERVICE);
+ dump("CONNECTION_ERROR", ErrorCode.CONNECTION_ERROR);
+ dump("UNKNOWN_SESSION", ErrorCode.UNKNOWN_SESSION);
+ dump("SESSION_BUSY", ErrorCode.SESSION_BUSY);
+ dump("SEND_ABORTED", ErrorCode.SEND_ABORTED);
+ dump("HANDSHAKE_FAILED", ErrorCode.HANDSHAKE_FAILED);
+ dump("first unused TRANSIENT_ERROR", ErrorCode.TRANSIENT_ERROR + 8);
+
+ dump("SEND_QUEUE_CLOSED", ErrorCode.SEND_QUEUE_CLOSED);
+ dump("ILLEGAL_ROUTE", ErrorCode.ILLEGAL_ROUTE);
+ dump("NO_SERVICES_FOR_ROUTE", ErrorCode.NO_SERVICES_FOR_ROUTE);
+ dump("SERVICE_OOS", ErrorCode.SERVICE_OOS);
+ dump("ENCODE_ERROR", ErrorCode.ENCODE_ERROR);
+ dump("NETWORK_ERROR", ErrorCode.NETWORK_ERROR);
+ dump("UNKNOWN_PROTOCOL", ErrorCode.UNKNOWN_PROTOCOL);
+ dump("DECODE_ERROR", ErrorCode.DECODE_ERROR);
+ dump("TIMEOUT", ErrorCode.TIMEOUT);
+ dump("INCOMPATIBLE_VERSION", ErrorCode.INCOMPATIBLE_VERSION);
+ dump("UNKNOWN_POLICY", ErrorCode.UNKNOWN_POLICY);
+ dump("NETWORK_SHUTDOWN", ErrorCode.NETWORK_SHUTDOWN);
+ dump("POLICY_ERROR", ErrorCode.POLICY_ERROR);
+ dump("SEQUENCE_ERROR", ErrorCode.SEQUENCE_ERROR);
+ dump("first unused FATAL_ERROR", ErrorCode.FATAL_ERROR + 15);
+
+ dump("max UNKNOWN below", ErrorCode.TRANSIENT_ERROR - 1);
+ dump("min TRANSIENT_ERROR", ErrorCode.TRANSIENT_ERROR);
+ dump("max TRANSIENT_ERROR", ErrorCode.TRANSIENT_ERROR + 49999);
+ dump("min APP_TRANSIENT_ERROR", ErrorCode.APP_TRANSIENT_ERROR);
+ dump("max APP_TRANSIENT_ERROR", ErrorCode.APP_TRANSIENT_ERROR + 49999);
+ dump("min FATAL_ERROR", ErrorCode.FATAL_ERROR);
+ dump("max FATAL_ERROR", ErrorCode.FATAL_ERROR + 49999);
+ dump("min APP_FATAL_ERROR", ErrorCode.APP_FATAL_ERROR);
+ dump("max APP_FATAL_ERROR", ErrorCode.APP_FATAL_ERROR + 49999);
+ dump("min UNKNOWN above", ErrorCode.ERROR_LIMIT);
+ }
+}
diff --git a/messagebus/test/src/tests/errorcodes/FILES b/messagebus/test/src/tests/errorcodes/FILES
new file mode 100644
index 00000000000..766402133fb
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/FILES
@@ -0,0 +1,5 @@
+dumpcodes.cpp
+DumpCodes.java
+ref-dump.txt
+cpp-dump.txt
+java-dump.txt
diff --git a/messagebus/test/src/tests/errorcodes/dumpcodes.cpp b/messagebus/test/src/tests/errorcodes/dumpcodes.cpp
new file mode 100644
index 00000000000..121d8585726
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/dumpcodes.cpp
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("dumpcodes");
+#include <vespa/messagebus/errorcode.h>
+#include <string>
+
+using namespace mbus;
+
+class App : public FastOS_Application
+{
+public:
+ void dump(const std::string &desc, uint32_t value);
+ int Main();
+};
+
+void
+App::dump(const std::string &desc, uint32_t value)
+{
+ fprintf(stdout, "%s => %u => \"%s\"\n", desc.c_str(), value,
+ ErrorCode::getName(value).c_str());
+}
+
+int
+App::Main()
+{
+ dump("NONE", ErrorCode::NONE);
+
+ dump("SEND_QUEUE_FULL", ErrorCode::SEND_QUEUE_FULL);
+ dump("NO_ADDRESS_FOR_SERVICE", ErrorCode::NO_ADDRESS_FOR_SERVICE);
+ dump("CONNECTION_ERROR", ErrorCode::CONNECTION_ERROR);
+ dump("UNKNOWN_SESSION", ErrorCode::UNKNOWN_SESSION);
+ dump("SESSION_BUSY", ErrorCode::SESSION_BUSY);
+ dump("SEND_ABORTED", ErrorCode::SEND_ABORTED);
+ dump("HANDSHAKE_FAILED", ErrorCode::HANDSHAKE_FAILED);
+ dump("first unused TRANSIENT_ERROR", ErrorCode::TRANSIENT_ERROR + 8);
+
+ dump("SEND_QUEUE_CLOSED", ErrorCode::SEND_QUEUE_CLOSED);
+ dump("ILLEGAL_ROUTE", ErrorCode::ILLEGAL_ROUTE);
+ dump("NO_SERVICES_FOR_ROUTE", ErrorCode::NO_SERVICES_FOR_ROUTE);
+ dump("SERVICE_OOS", ErrorCode::SERVICE_OOS);
+ dump("ENCODE_ERROR", ErrorCode::ENCODE_ERROR);
+ dump("NETWORK_ERROR", ErrorCode::NETWORK_ERROR);
+ dump("UNKNOWN_PROTOCOL", ErrorCode::UNKNOWN_PROTOCOL);
+ dump("DECODE_ERROR", ErrorCode::DECODE_ERROR);
+ dump("TIMEOUT", ErrorCode::TIMEOUT);
+ dump("INCOMPATIBLE_VERSION", ErrorCode::INCOMPATIBLE_VERSION);
+ dump("UNKNOWN_POLICY", ErrorCode::UNKNOWN_POLICY);
+ dump("NETWORK_SHUTDOWN", ErrorCode::NETWORK_SHUTDOWN);
+ dump("POLICY_ERROR", ErrorCode::POLICY_ERROR);
+ dump("SEQUENCE_ERROR", ErrorCode::SEQUENCE_ERROR);
+ dump("first unused FATAL_ERROR", ErrorCode::FATAL_ERROR + 15);
+
+ dump("max UNKNOWN below", ErrorCode::TRANSIENT_ERROR - 1);
+ dump("min TRANSIENT_ERROR", ErrorCode::TRANSIENT_ERROR);
+ dump("max TRANSIENT_ERROR", ErrorCode::TRANSIENT_ERROR + 49999);
+ dump("min APP_TRANSIENT_ERROR", ErrorCode::APP_TRANSIENT_ERROR);
+ dump("max APP_TRANSIENT_ERROR", ErrorCode::APP_TRANSIENT_ERROR + 49999);
+ dump("min FATAL_ERROR", ErrorCode::FATAL_ERROR);
+ dump("max FATAL_ERROR", ErrorCode::FATAL_ERROR + 49999);
+ dump("min APP_FATAL_ERROR", ErrorCode::APP_FATAL_ERROR);
+ dump("max APP_FATAL_ERROR", ErrorCode::APP_FATAL_ERROR + 49999);
+ dump("min UNKNOWN above", ErrorCode::ERROR_LIMIT);
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ App app;
+ return app.Entry(argc, argv);
+}
diff --git a/messagebus/test/src/tests/errorcodes/errorcodes_test.sh b/messagebus/test/src/tests/errorcodes/errorcodes_test.sh
new file mode 100644
index 00000000000..186de0a5033
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/errorcodes_test.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+. ../../binref/env.sh
+
+$BINREF/compilejava DumpCodes.java
+
+./messagebus_test_dumpcodes_app > cpp-dump.txt
+$BINREF/runjava DumpCodes > java-dump.txt
+diff -u ref-dump.txt cpp-dump.txt
+diff -u ref-dump.txt java-dump.txt
diff --git a/messagebus/test/src/tests/errorcodes/ref-dump.txt b/messagebus/test/src/tests/errorcodes/ref-dump.txt
new file mode 100644
index 00000000000..b8038816897
--- /dev/null
+++ b/messagebus/test/src/tests/errorcodes/ref-dump.txt
@@ -0,0 +1,34 @@
+NONE => 0 => "NONE"
+SEND_QUEUE_FULL => 100001 => "SEND_QUEUE_FULL"
+NO_ADDRESS_FOR_SERVICE => 100002 => "NO_ADDRESS_FOR_SERVICE"
+CONNECTION_ERROR => 100003 => "CONNECTION_ERROR"
+UNKNOWN_SESSION => 100004 => "UNKNOWN_SESSION"
+SESSION_BUSY => 100005 => "SESSION_BUSY"
+SEND_ABORTED => 100006 => "SEND_ABORTED"
+HANDSHAKE_FAILED => 100007 => "HANDSHAKE_FAILED"
+first unused TRANSIENT_ERROR => 100008 => "UNKNOWN(100008)"
+SEND_QUEUE_CLOSED => 200001 => "SEND_QUEUE_CLOSED"
+ILLEGAL_ROUTE => 200002 => "ILLEGAL_ROUTE"
+NO_SERVICES_FOR_ROUTE => 200003 => "NO_SERVICES_FOR_ROUTE"
+SERVICE_OOS => 200004 => "SERVICE_OOS"
+ENCODE_ERROR => 200005 => "ENCODE_ERROR"
+NETWORK_ERROR => 200006 => "NETWORK_ERROR"
+UNKNOWN_PROTOCOL => 200007 => "UNKNOWN_PROTOCOL"
+DECODE_ERROR => 200008 => "DECODE_ERROR"
+TIMEOUT => 200009 => "TIMEOUT"
+INCOMPATIBLE_VERSION => 200010 => "INCOMPATIBLE_VERSION"
+UNKNOWN_POLICY => 200011 => "UNKNOWN_POLICY"
+NETWORK_SHUTDOWN => 200012 => "NETWORK_SHUTDOWN"
+POLICY_ERROR => 200013 => "POLICY_ERROR"
+SEQUENCE_ERROR => 200014 => "SEQUENCE_ERROR"
+first unused FATAL_ERROR => 200015 => "UNKNOWN(200015)"
+max UNKNOWN below => 99999 => "UNKNOWN(99999)"
+min TRANSIENT_ERROR => 100000 => "TRANSIENT_ERROR"
+max TRANSIENT_ERROR => 149999 => "UNKNOWN(149999)"
+min APP_TRANSIENT_ERROR => 150000 => "APP_TRANSIENT_ERROR"
+max APP_TRANSIENT_ERROR => 199999 => "UNKNOWN(199999)"
+min FATAL_ERROR => 200000 => "FATAL_ERROR"
+max FATAL_ERROR => 249999 => "UNKNOWN(249999)"
+min APP_FATAL_ERROR => 250000 => "APP_FATAL_ERROR"
+max APP_FATAL_ERROR => 299999 => "UNKNOWN(299999)"
+min UNKNOWN above => 300000 => "UNKNOWN(300000)"
diff --git a/messagebus/test/src/tests/speed/.gitignore b/messagebus/test/src/tests/speed/.gitignore
new file mode 100644
index 00000000000..326da75ebb6
--- /dev/null
+++ b/messagebus/test/src/tests/speed/.gitignore
@@ -0,0 +1,15 @@
+*.class
+.depend
+Makefile
+cpp-client
+cpp-server
+out.*
+pid.*
+routing.cfg
+slobrok.cfg
+speed_test
+cpp-client-speed
+cpp-server-speed
+messagebus_test_speed_test_app
+messagebus_test_cpp-client-speed_app
+messagebus_test_cpp-server-speed_app
diff --git a/messagebus/test/src/tests/speed/CMakeLists.txt b/messagebus/test/src/tests/speed/CMakeLists.txt
new file mode 100644
index 00000000000..8e1018ec07c
--- /dev/null
+++ b/messagebus/test/src/tests/speed/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_test_speed_test_app
+ SOURCES
+ speed.cpp
+ DEPENDS
+)
+vespa_add_test(NAME messagebus_test_speed_test_app COMMAND messagebus_test_speed_test_app BENCHMARK)
+vespa_add_executable(messagebus_test_cpp-server-speed_app
+ SOURCES
+ cpp-server.cpp
+ DEPENDS
+)
+vespa_add_executable(messagebus_test_cpp-client-speed_app
+ SOURCES
+ cpp-client.cpp
+ DEPENDS
+)
diff --git a/messagebus/test/src/tests/speed/DESC b/messagebus/test/src/tests/speed/DESC
new file mode 100644
index 00000000000..10734957438
--- /dev/null
+++ b/messagebus/test/src/tests/speed/DESC
@@ -0,0 +1,4 @@
+This is a simple test that gives a rough idea of the inherent overhead
+in messagebus. It sends simple messages back and forth with the
+simplest routing setup possible. This test also tests that messagebus
+works across Java and C++.
diff --git a/messagebus/test/src/tests/speed/FILES b/messagebus/test/src/tests/speed/FILES
new file mode 100644
index 00000000000..09f0a5ec1d3
--- /dev/null
+++ b/messagebus/test/src/tests/speed/FILES
@@ -0,0 +1,8 @@
+speed.cpp
+out.server.cpp
+out.server.java
+cpp-client.cpp
+cpp-server.cpp
+JavaClient.java
+JavaServer.java
+routing-template.cfg
diff --git a/messagebus/test/src/tests/speed/JavaClient.java b/messagebus/test/src/tests/speed/JavaClient.java
new file mode 100644
index 00000000000..b905ab07e91
--- /dev/null
+++ b/messagebus/test/src/tests/speed/JavaClient.java
@@ -0,0 +1,137 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.test.*;
+import com.yahoo.config.*;
+import com.yahoo.messagebus.routing.*;
+import com.yahoo.messagebus.network.*;
+import com.yahoo.messagebus.network.rpc.*;
+import java.util.Arrays;
+import java.util.logging.*;
+
+public class JavaClient implements ReplyHandler {
+
+ private static Logger log = Logger.getLogger(JavaClient.class.getName());
+
+ private static class Counts {
+ public int okCnt = 0;
+ public int failCnt = 0;
+ Counts() {}
+ Counts(int okCnt, int failCnt) {
+ this.okCnt = okCnt;
+ this.failCnt = failCnt;
+ }
+ }
+
+ private SourceSession session;
+ private Counts counts = new Counts();
+ private static long mySeq = 100000;
+
+ public JavaClient(RPCMessageBus mb) {
+ session = mb.getMessageBus().createSourceSession(this, new SourceSessionParams().setTimeout(30));
+ }
+
+ public synchronized Counts sample() {
+ return new Counts(counts.okCnt, counts.failCnt);
+ }
+
+ public void send() {
+ send(++mySeq);
+ }
+
+ public void send(long seq) {
+ session.send(new MyMessage(seq), "test");
+ }
+
+ public void handleReply(Reply reply) {
+ if ((reply.getProtocol() == SimpleProtocol.NAME)
+ && (reply.getType() == SimpleProtocol.REPLY)
+ && (((SimpleReply)reply).getValue().equals("OK")))
+ {
+ synchronized (this) {
+ counts.okCnt++;
+ }
+ } else {
+ synchronized (this) {
+ counts.failCnt++;
+ }
+ }
+ try {
+ send();
+ } catch (IllegalStateException ignore) {} // handle paranoia for shutdown source sessions
+ }
+
+ public void shutdown() {
+ session.destroy();
+ }
+
+ public static void main(String[] args) {
+ try {
+ RPCMessageBus mb = new RPCMessageBus(
+ new MessageBusParams()
+ .setRetryPolicy(new RetryTransientErrorsPolicy().setBaseDelay(0.1))
+ .addProtocol(new SimpleProtocol()),
+ new RPCNetworkParams()
+ .setIdentity(new Identity("server/java"))
+ .setSlobrokConfigId("file:slobrok.cfg"),
+ "file:routing.cfg");
+ JavaClient client = new JavaClient(mb);
+
+ // let the system 'warm up'
+ Thread.sleep(5000);
+
+ // inject messages into the feedback loop
+ for (int i = 0; i < 1024; ++i) {
+ client.send(i);
+ }
+
+ // let the system 'warm up'
+ Thread.sleep(5000);
+
+ long start;
+ long stop;
+ Counts before;
+ Counts after;
+
+ start = System.currentTimeMillis();
+ before = client.sample();
+ Thread.sleep(10000); // Benchmark time
+ stop = System.currentTimeMillis();
+ after = client.sample();
+ stop -= start;
+ double time = (double)stop;
+ double msgCnt = (double)(after.okCnt - before.okCnt);
+ double throughput = (msgCnt / time) * 1000.0;
+ System.out.printf("JAVA-CLIENT: %g msg/s\n", throughput);
+ client.shutdown();
+ mb.destroy();
+ if (after.failCnt > before.failCnt) {
+ System.err.printf("JAVA-CLIENT: FAILED (%d -> %d)\n",
+ before.failCnt, after.failCnt);
+ System.exit(1);
+ }
+ } catch (Exception e) {
+ log.log(Level.SEVERE, "JAVA-CLIENT: Failed", e);
+ System.exit(1);
+ }
+ }
+
+ private static class MyMessage extends SimpleMessage {
+
+ final long seqId;
+
+ MyMessage(long seqId) {
+ super("message");
+ this.seqId = seqId;
+ }
+
+ @Override
+ public boolean hasSequenceId() {
+ return true;
+ }
+
+ @Override
+ public long getSequenceId() {
+ return seqId;
+ }
+ }
+}
diff --git a/messagebus/test/src/tests/speed/JavaServer.java b/messagebus/test/src/tests/speed/JavaServer.java
new file mode 100644
index 00000000000..afec6dcdba2
--- /dev/null
+++ b/messagebus/test/src/tests/speed/JavaServer.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.test.*;
+import com.yahoo.config.*;
+import com.yahoo.messagebus.routing.*;
+import com.yahoo.messagebus.network.*;
+import com.yahoo.messagebus.network.rpc.*;
+import java.util.Arrays;
+import java.util.logging.*;
+
+public class JavaServer implements MessageHandler {
+
+ private static Logger log = Logger.getLogger(JavaServer.class.getName());
+
+ private DestinationSession session;
+
+ public JavaServer(RPCMessageBus mb) {
+ session = mb.getMessageBus().createDestinationSession("session", true, this);
+ }
+
+ public void handleMessage(Message msg) {
+ if ((msg.getProtocol() == SimpleProtocol.NAME)
+ && (msg.getType() == SimpleProtocol.MESSAGE)
+ && (((SimpleMessage)msg).getValue().equals("message")))
+ {
+ Reply reply = new SimpleReply("OK");
+ msg.swapState(reply);
+ session.reply(reply);
+ } else {
+ Reply reply = new SimpleReply("FAIL");
+ msg.swapState(reply);
+ session.reply(reply);
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ RPCMessageBus mb = new RPCMessageBus(
+ Arrays.asList((Protocol)new SimpleProtocol()),
+ new RPCNetworkParams()
+ .setIdentity(new Identity("server/java"))
+ .setSlobrokConfigId("file:slobrok.cfg"),
+ "file:routing.cfg");
+ JavaServer server = new JavaServer(mb);
+ System.out.println("java server started");
+ while (true) {
+ Thread.sleep(1000);
+ }
+ } catch (Exception e) {
+ log.log(Level.SEVERE, "JAVA-SERVER: Failed", e);
+ System.exit(1);
+ }
+ }
+}
diff --git a/messagebus/test/src/tests/speed/cpp-client.cpp b/messagebus/test/src/tests/speed/cpp-client.cpp
new file mode 100644
index 00000000000..c0c9d621a20
--- /dev/null
+++ b/messagebus/test/src/tests/speed/cpp-client.cpp
@@ -0,0 +1,146 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("cpp-client");
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/routing/retrytransienterrorspolicy.h>
+#include <vespa/messagebus/rpcmessagebus.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/vespalib/util/sync.h>
+
+using namespace mbus;
+
+class Client : public IReplyHandler
+{
+private:
+ vespalib::Lock _lock;
+ uint32_t _okCnt;
+ uint32_t _failCnt;
+ SourceSession::UP _session;
+ static uint64_t _seq;
+public:
+ Client(MessageBus &bus, const SourceSessionParams &params);
+ ~Client();
+ void send();
+ void send(uint64_t seq);
+ void sample(uint32_t &okCnt, uint32_t &failCnt);
+ void handleReply(Reply::UP reply);
+};
+uint64_t Client::_seq = 100000;
+
+Client::Client(MessageBus &bus, const SourceSessionParams &params)
+ : _lock(),
+ _okCnt(0),
+ _failCnt(0),
+ _session(bus.createSourceSession(*this, params))
+{
+}
+
+Client::~Client()
+{
+ _session->close();
+}
+
+void
+Client::send() {
+ send(++_seq);
+}
+
+void
+Client::send(uint64_t seq) {
+ Message::UP msg(new SimpleMessage("message", true, seq));
+ _session->send(std::move(msg), "test");
+}
+
+void
+Client::sample(uint32_t &okCnt, uint32_t &failCnt) {
+ vespalib::LockGuard guard(_lock);
+ okCnt = _okCnt;
+ failCnt = _failCnt;
+}
+
+void
+Client::handleReply(Reply::UP reply) {
+ if ((reply->getProtocol() == SimpleProtocol::NAME)
+ && (reply->getType() == SimpleProtocol::REPLY)
+ && (static_cast<SimpleReply&>(*reply).getValue() == "OK"))
+ {
+ vespalib::LockGuard guard(_lock);
+ ++_okCnt;
+ } else {
+ fprintf(stderr, "BAD REPLY\n");
+ for (uint32_t i = 0; i < reply->getNumErrors(); ++i) {
+ fprintf(stderr, "ERR[%d]: code=%d, msg=%s\n", i,
+ reply->getError(i).getCode(),
+ reply->getError(i).getMessage().c_str());
+ }
+ vespalib::LockGuard guard(_lock);
+ ++_failCnt;
+ }
+ send();
+}
+
+class App : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+int
+App::Main()
+{
+ RetryTransientErrorsPolicy::SP retryPolicy(new RetryTransientErrorsPolicy());
+ retryPolicy->setBaseDelay(0.1);
+ RPCMessageBus mb(MessageBusParams().setRetryPolicy(retryPolicy).addProtocol(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setIdentity(Identity("server/cpp")).setSlobrokConfig("file:slobrok.cfg"),
+ "file:routing.cfg");
+ Client client(mb.getMessageBus(), SourceSessionParams().setTimeout(30));
+
+ // let the system 'warm up'
+ FastOS_Thread::Sleep(5000);
+
+ // inject messages into the feedback loop
+ for (uint32_t i = 0; i < 1024; ++i) {
+ client.send(i);
+ }
+
+ // let the system 'warm up'
+ FastOS_Thread::Sleep(5000);
+
+ FastOS_Time start;
+ FastOS_Time stop;
+ uint32_t okBefore = 0;
+ uint32_t okAfter = 0;
+ uint32_t failBefore = 0;
+ uint32_t failAfter = 0;
+
+ start.SetNow();
+ client.sample(okBefore, failBefore);
+ FastOS_Thread::Sleep(10000); // Benchmark time
+ stop.SetNow();
+ client.sample(okAfter, failAfter);
+ stop -= start;
+ double time = stop.MilliSecs();
+ double msgCnt = (double)(okAfter - okBefore);
+ double throughput = (msgCnt / time) * 1000.0;
+ fprintf(stdout, "CPP-CLIENT: %g msg/s\n", throughput);
+ if (failAfter > failBefore) {
+ fprintf(stderr, "CPP-CLIENT: FAILED (%d -> %d)\n", failBefore, failAfter);
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ fprintf(stderr, "started '%s'\n", argv[0]);
+ fflush(stderr);
+ App app;
+ int r = app.Entry(argc, argv);
+ fprintf(stderr, "stopping '%s'\n", argv[0]);
+ fflush(stderr);
+ return r;
+}
diff --git a/messagebus/test/src/tests/speed/cpp-server.cpp b/messagebus/test/src/tests/speed/cpp-server.cpp
new file mode 100644
index 00000000000..c2cd9bf262a
--- /dev/null
+++ b/messagebus/test/src/tests/speed/cpp-server.cpp
@@ -0,0 +1,77 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("cpp-server");
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/rpcmessagebus.h>
+#include <vespa/messagebus/iprotocol.h>
+#include <vespa/messagebus/protocolset.h>
+
+using namespace mbus;
+
+class Server : public IMessageHandler
+{
+private:
+ DestinationSession::UP _session;
+public:
+ Server(MessageBus &bus);
+ ~Server();
+ void handleMessage(Message::UP msg);
+};
+
+Server::Server(MessageBus &bus)
+ : _session(bus.createDestinationSession("session", true, *this))
+{
+ fprintf(stderr, "cpp server started\n");
+}
+
+Server::~Server()
+{
+ _session.reset();
+}
+
+void
+Server::handleMessage(Message::UP msg) {
+ if ((msg->getProtocol() == SimpleProtocol::NAME)
+ && (msg->getType() == SimpleProtocol::MESSAGE)
+ && (static_cast<SimpleMessage&>(*msg).getValue() == "message"))
+ {
+ Reply::UP reply(new SimpleReply("OK"));
+ msg->swapState(*reply);
+ _session->reply(std::move(reply));
+ } else {
+ Reply::UP reply(new SimpleReply("FAIL"));
+ msg->swapState(*reply);
+ _session->reply(std::move(reply));
+ }
+}
+
+class App : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+int
+App::Main()
+{
+ RPCMessageBus mb(ProtocolSet().add(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity("server/cpp"))
+ .setSlobrokConfig("file:slobrok.cfg"),
+ "file:routing.cfg");
+ Server server(mb.getMessageBus());
+ while (true) {
+ FastOS_Thread::Sleep(1000);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ App app;
+ return app.Entry(argc, argv);
+}
diff --git a/messagebus/test/src/tests/speed/ctl.sh b/messagebus/test/src/tests/speed/ctl.sh
new file mode 100755
index 00000000000..ea969749808
--- /dev/null
+++ b/messagebus/test/src/tests/speed/ctl.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+sh ../../binref/progctl.sh progdefs.sh "$@"
diff --git a/messagebus/test/src/tests/speed/progdefs.sh b/messagebus/test/src/tests/speed/progdefs.sh
new file mode 100644
index 00000000000..4e0390142cf
--- /dev/null
+++ b/messagebus/test/src/tests/speed/progdefs.sh
@@ -0,0 +1,3 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+prog server cpp "" "./messagebus_test_cpp-server-speed_app"
+prog server java "" "../../binref/runjava JavaServer"
diff --git a/messagebus/test/src/tests/speed/routing-template.cfg b/messagebus/test/src/tests/speed/routing-template.cfg
new file mode 100644
index 00000000000..4b938c9cc82
--- /dev/null
+++ b/messagebus/test/src/tests/speed/routing-template.cfg
@@ -0,0 +1,11 @@
+routingtable[1]
+routingtable[0].protocol "Simple"
+routingtable[0].hop[1]
+routingtable[0].hop[0].name "server"
+routingtable[0].hop[0].selector "server/session"
+routingtable[0].hop[0].recipient[1]
+routingtable[0].hop[0].recipient[0] "server/session"
+routingtable[0].route[1]
+routingtable[0].route[0].name "test"
+routingtable[0].route[0].hop[1]
+routingtable[0].route[0].hop[0] "server"
diff --git a/messagebus/test/src/tests/speed/speed.cpp b/messagebus/test/src/tests/speed/speed.cpp
new file mode 100644
index 00000000000..31ea419ce5c
--- /dev/null
+++ b/messagebus/test/src/tests/speed/speed.cpp
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("speed_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
+using namespace mbus;
+using vespalib::make_string;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("speed_test");
+ Slobrok slobrok;
+ { // Make slobrok config
+ EXPECT_EQUAL(system("echo slobrok[1] > slobrok.cfg"), 0);
+ EXPECT_EQUAL(system(make_string("echo 'slobrok[0].connectionspec tcp/localhost:%d' "
+ ">> slobrok.cfg", slobrok.port()).c_str()), 0);
+ }
+ { // CPP SERVER
+ { // Make routing config
+ EXPECT_EQUAL(system("cat routing-template.cfg | sed 's#session#cpp/session#' > routing.cfg"), 0);
+ }
+ fprintf(stderr, "STARTING CPP-SERVER\n");
+ EXPECT_EQUAL(system("sh ctl.sh start server cpp"), 0);
+ fprintf(stderr, "STARTING CPP-CLIENT\n");
+ EXPECT_EQUAL(system("./messagebus_test_cpp-client-speed_app"), 0);
+ fprintf(stderr, "STARTING JAVA-CLIENT\n");
+ EXPECT_EQUAL(system("../../binref/runjava JavaClient"), 0);
+ fprintf(stderr, "STOPPING\n");
+ EXPECT_EQUAL(system("sh ctl.sh stop server cpp"), 0);
+ }
+ { // JAVA SERVER
+ { // Make routing config
+ EXPECT_EQUAL(system("cat routing-template.cfg | sed 's#session#java/session#' > routing.cfg"), 0);
+ }
+ fprintf(stderr, "STARTING JAVA-SERVER\n");
+ EXPECT_EQUAL(system("sh ctl.sh start server java"), 0);
+ fprintf(stderr, "STARTING CPP-CLIENT\n");
+ EXPECT_EQUAL(system("./messagebus_test_cpp-client-speed_app"), 0);
+ fprintf(stderr, "STARTING JAVA-CLIENT\n");
+ EXPECT_EQUAL(system("../../binref/runjava JavaClient"), 0);
+ fprintf(stderr, "STOPPING\n");
+ EXPECT_EQUAL(system("sh ctl.sh stop server java"), 0);
+ }
+ TEST_DONE();
+}
diff --git a/messagebus/test/src/tests/speed/speed_test.sh b/messagebus/test/src/tests/speed/speed_test.sh
new file mode 100644
index 00000000000..f9789961fa7
--- /dev/null
+++ b/messagebus/test/src/tests/speed/speed_test.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+. ../../binref/env.sh
+
+$BINREF/compilejava JavaServer.java
+$BINREF/compilejava JavaClient.java
+
+(ulimit -c; ulimit -H -c; ulimit -c unlimited; ./messagebus_test_speed_test_app)
diff --git a/messagebus/test/src/tests/trace/.gitignore b/messagebus/test/src/tests/trace/.gitignore
new file mode 100644
index 00000000000..1be907ec2f8
--- /dev/null
+++ b/messagebus/test/src/tests/trace/.gitignore
@@ -0,0 +1,12 @@
+*.class
+.depend
+Makefile
+cpp-server
+out.*
+pid.*
+routing.cfg
+slobrok.cfg
+trace_test
+/cpp-server-trace
+messagebus_test_trace_test_app
+messagebus_test_cpp-server-trace_app
diff --git a/messagebus/test/src/tests/trace/CMakeLists.txt b/messagebus/test/src/tests/trace/CMakeLists.txt
new file mode 100644
index 00000000000..5cebc9ae667
--- /dev/null
+++ b/messagebus/test/src/tests/trace/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(messagebus_test_trace_test_app
+ SOURCES
+ trace.cpp
+ DEPENDS
+)
+vespa_add_test(NAME messagebus_test_trace_test_app NO_VALGRIND COMMAND sh trace_test.sh)
+vespa_add_executable(messagebus_test_cpp-server-trace_app
+ SOURCES
+ cpp-server.cpp
+ DEPENDS
+)
diff --git a/messagebus/test/src/tests/trace/DESC b/messagebus/test/src/tests/trace/DESC
new file mode 100644
index 00000000000..452e75aefea
--- /dev/null
+++ b/messagebus/test/src/tests/trace/DESC
@@ -0,0 +1 @@
+trace test. Take a look at trace.cpp for details.
diff --git a/messagebus/test/src/tests/trace/FILES b/messagebus/test/src/tests/trace/FILES
new file mode 100644
index 00000000000..891e4df6273
--- /dev/null
+++ b/messagebus/test/src/tests/trace/FILES
@@ -0,0 +1,19 @@
+trace.cpp
+cpp-server.cpp
+JavaServer.java
+routing.cfg
+out.server.cpp1
+out.server.cpp2
+out.server.cpp3
+out.server.cpp4
+out.server.cpp5
+out.server.cpp6
+out.server.cpp7
+out.server.java1
+out.server.java2
+out.server.java3
+out.server.java4
+out.server.java5
+out.server.java6
+out.server.java7
+progdefs.sh
diff --git a/messagebus/test/src/tests/trace/JavaServer.java b/messagebus/test/src/tests/trace/JavaServer.java
new file mode 100644
index 00000000000..5dfe15e3d0b
--- /dev/null
+++ b/messagebus/test/src/tests/trace/JavaServer.java
@@ -0,0 +1,97 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+import com.yahoo.messagebus.*;
+import com.yahoo.messagebus.test.*;
+import com.yahoo.config.*;
+import com.yahoo.messagebus.routing.*;
+import com.yahoo.messagebus.network.*;
+import com.yahoo.messagebus.network.rpc.*;
+import java.util.Arrays;
+import java.util.logging.*;
+
+public class JavaServer implements MessageHandler, ReplyHandler {
+
+ private static Logger log = Logger.getLogger(JavaServer.class.getName());
+
+ private IntermediateSession session;
+ private String name;
+
+ public JavaServer(RPCMessageBus mb, String name) {
+ session = mb.getMessageBus().createIntermediateSession("session", true, this, this);
+ this.name = name;
+ }
+
+ public void handleMessage(Message msg) {
+ msg.getTrace().trace(1, name + " (message)", false);
+ if (msg.getRoute() == null || !msg.getRoute().hasHops()) {
+ System.out.println("**** Server '" + name + "' replying.");
+ Reply reply = new EmptyReply();
+ msg.swapState(reply);
+ handleReply(reply);
+ } else {
+ System.out.println("**** Server '" + name + "' forwarding message.");
+ session.forward(msg);
+ }
+ }
+
+ public void handleReply(Reply reply) {
+ reply.getTrace().trace(1, name + " (reply)", false);
+ session.forward(reply);
+ }
+
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ System.err.println("usage: JavaServer <service prefix>");
+ System.exit(1);
+ }
+ String name = args[0];
+ SimpleProtocol protocol = new SimpleProtocol();
+ protocol.addPolicyFactory("All", new SimpleProtocol.PolicyFactory() {
+ @Override
+ public RoutingPolicy create(String param) {
+ return new AllPolicy();
+ }
+ });
+ try {
+ RPCMessageBus mb = new RPCMessageBus(
+ Arrays.<Protocol>asList(protocol),
+ new RPCNetworkParams()
+ .setIdentity(new Identity(name))
+ .setSlobrokConfigId("file:slobrok.cfg"),
+ "file:routing.cfg");
+ JavaServer server = new JavaServer(mb, name);
+ System.out.printf("java server started name=%s\n", name);
+ while (true) {
+ Thread.sleep(1000);
+ }
+ } catch (Exception e) {
+ log.log(Level.SEVERE, "JAVA-SERVER: Failed", e);
+ System.exit(1);
+ }
+ }
+
+ private static class AllPolicy implements RoutingPolicy {
+
+ @Override
+ public void select(RoutingContext ctx) {
+ ctx.addChildren(ctx.getMatchedRecipients());
+ }
+
+ @Override
+ public void merge(RoutingContext ctx) {
+ EmptyReply ret = new EmptyReply();
+ for (RoutingNodeIterator it = ctx.getChildIterator();
+ it.isValid(); it.next()) {
+ Reply reply = it.getReplyRef();
+ for (int i = 0; i < reply.getNumErrors(); ++i) {
+ ret.addError(reply.getError(i));
+ }
+ }
+ ctx.setReply(ret);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ }
+}
diff --git a/messagebus/test/src/tests/trace/cpp-server.cpp b/messagebus/test/src/tests/trace/cpp-server.cpp
new file mode 100644
index 00000000000..76e20bc3cfd
--- /dev/null
+++ b/messagebus/test/src/tests/trace/cpp-server.cpp
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("cpp-server");
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/rpcmessagebus.h>
+#include <vespa/messagebus/iprotocol.h>
+#include <vespa/messagebus/protocolset.h>
+#include <vespa/messagebus/emptyreply.h>
+
+using namespace mbus;
+
+class Server : public IMessageHandler,
+ public IReplyHandler
+{
+private:
+ IntermediateSession::UP _session;
+ std::string _name;
+public:
+ Server(MessageBus &bus, const std::string &name);
+ ~Server();
+ void handleMessage(Message::UP msg);
+ void handleReply(Reply::UP reply);
+};
+
+Server::Server(MessageBus &bus, const std::string &name)
+ : _session(bus.createIntermediateSession("session", true, *this, *this)),
+ _name(name)
+{
+ fprintf(stderr, "cpp server started: %s\n", _name.c_str());
+}
+
+Server::~Server()
+{
+ _session.reset();
+}
+
+void
+Server::handleMessage(Message::UP msg) {
+ msg->getTrace().trace(1, _name + " (message)", false);
+ if (!msg->getRoute().hasHops()) {
+ fprintf(stderr, "**** Server '%s' replying.\n", _name.c_str());
+ Reply::UP reply(new EmptyReply());
+ msg->swapState(*reply);
+ handleReply(std::move(reply));
+ } else {
+ fprintf(stderr, "**** Server '%s' forwarding message.\n", _name.c_str());
+ _session->forward(std::move(msg));
+ }
+}
+
+void
+Server::handleReply(Reply::UP reply) {
+ reply->getTrace().trace(1, _name + " (reply)", false);
+ _session->forward(std::move(reply));
+}
+
+class App : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+int
+App::Main()
+{
+ if (_argc != 2) {
+ fprintf(stderr, "usage: %s <service-prefix>\n", _argv[0]);
+ return 1;
+ }
+ RPCMessageBus mb(ProtocolSet().add(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams()
+ .setIdentity(Identity(_argv[1]))
+ .setSlobrokConfig("file:slobrok.cfg"),
+ "file:routing.cfg");
+ Server server(mb.getMessageBus(), _argv[1]);
+ while (true) {
+ FastOS_Thread::Sleep(1000);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ App app;
+ return app.Entry(argc, argv);
+}
diff --git a/messagebus/test/src/tests/trace/ctl.sh b/messagebus/test/src/tests/trace/ctl.sh
new file mode 100755
index 00000000000..ea969749808
--- /dev/null
+++ b/messagebus/test/src/tests/trace/ctl.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+sh ../../binref/progctl.sh progdefs.sh "$@"
diff --git a/messagebus/test/src/tests/trace/progdefs.sh b/messagebus/test/src/tests/trace/progdefs.sh
new file mode 100644
index 00000000000..fd35b6503e2
--- /dev/null
+++ b/messagebus/test/src/tests/trace/progdefs.sh
@@ -0,0 +1,15 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+prog server cpp1 "" "./messagebus_test_cpp-server-trace_app server/cpp/1/A"
+prog server cpp2 "" "./messagebus_test_cpp-server-trace_app server/cpp/2/A"
+prog server cpp3 "" "./messagebus_test_cpp-server-trace_app server/cpp/2/B"
+prog server cpp4 "" "./messagebus_test_cpp-server-trace_app server/cpp/3/A"
+prog server cpp5 "" "./messagebus_test_cpp-server-trace_app server/cpp/3/B"
+prog server cpp6 "" "./messagebus_test_cpp-server-trace_app server/cpp/3/C"
+prog server cpp7 "" "./messagebus_test_cpp-server-trace_app server/cpp/3/D"
+prog server java1 "" "../../binref/runjava JavaServer server/java/1/A"
+prog server java2 "" "../../binref/runjava JavaServer server/java/2/A"
+prog server java3 "" "../../binref/runjava JavaServer server/java/2/B"
+prog server java4 "" "../../binref/runjava JavaServer server/java/3/A"
+prog server java5 "" "../../binref/runjava JavaServer server/java/3/B"
+prog server java6 "" "../../binref/runjava JavaServer server/java/3/C"
+prog server java7 "" "../../binref/runjava JavaServer server/java/3/D"
diff --git a/messagebus/test/src/tests/trace/trace.cpp b/messagebus/test/src/tests/trace/trace.cpp
new file mode 100644
index 00000000000..94550460c84
--- /dev/null
+++ b/messagebus/test/src/tests/trace/trace.cpp
@@ -0,0 +1,113 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("trace_test");
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/sourcesession.h>
+#include <vespa/messagebus/rpcmessagebus.h>
+#include <vespa/messagebus/intermediatesession.h>
+#include <vespa/messagebus/destinationsession.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/messagebus/routing/routingspec.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/sourcesessionparams.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <iostream>
+
+using namespace mbus;
+using vespalib::make_string;
+
+TEST_SETUP(Test);
+
+bool
+waitSlobrok(RPCMessageBus &mbus, const std::string &pattern)
+{
+ for (int i = 0; i < 30000; i++) {
+ slobrok::api::MirrorAPI::SpecList res = mbus.getRPCNetwork().getMirror().lookup(pattern);
+ if (res.size() > 0) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ return false;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("trace_test");
+ Slobrok slobrok;
+ { // Make slobrok config
+ EXPECT_TRUE(system("echo slobrok[1] > slobrok.cfg") == 0);
+ EXPECT_TRUE(system(make_string("echo 'slobrok[0].connectionspec tcp/localhost:%d' "
+ ">> slobrok.cfg", slobrok.port()).c_str()) == 0);
+ }
+ EXPECT_TRUE(system("sh ctl.sh start all") == 0);
+ RPCMessageBus mb(ProtocolSet().add(IProtocol::SP(new SimpleProtocol())),
+ RPCNetworkParams().setSlobrokConfig("file:slobrok.cfg"),
+ "file:routing.cfg");
+ EXPECT_TRUE(waitSlobrok(mb, "server/cpp/1/A/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/cpp/2/A/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/cpp/2/B/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/cpp/3/A/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/cpp/3/B/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/cpp/3/C/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/cpp/3/D/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/java/1/A/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/java/2/A/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/java/2/B/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/java/3/A/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/java/3/B/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/java/3/C/session"));
+ EXPECT_TRUE(waitSlobrok(mb, "server/java/3/D/session"));
+
+ TraceNode e3 = TraceNode()
+ .addChild(TraceNode().addChild("server/cpp/3/A (message)").addChild("server/cpp/3/A (reply)"))
+ .addChild(TraceNode().addChild("server/cpp/3/B (message)").addChild("server/cpp/3/B (reply)"))
+ .addChild(TraceNode().addChild("server/cpp/3/C (message)").addChild("server/cpp/3/C (reply)"))
+ .addChild(TraceNode().addChild("server/cpp/3/D (message)").addChild("server/cpp/3/D (reply)"))
+ .addChild(TraceNode().addChild("server/java/3/A (message)").addChild("server/java/3/A (reply)"))
+ .addChild(TraceNode().addChild("server/java/3/B (message)").addChild("server/java/3/B (reply)"))
+ .addChild(TraceNode().addChild("server/java/3/C (message)").addChild("server/java/3/C (reply)"))
+ .addChild(TraceNode().addChild("server/java/3/D (message)").addChild("server/java/3/D (reply)")).setStrict(false);
+ TraceNode e2 = TraceNode()
+ .addChild(TraceNode().addChild("server/cpp/2/A (message)").addChild(e3).addChild("server/cpp/2/A (reply)"))
+ .addChild(TraceNode().addChild("server/cpp/2/B (message)").addChild(e3).addChild("server/cpp/2/B (reply)"))
+ .addChild(TraceNode().addChild("server/java/2/A (message)").addChild(e3).addChild("server/java/2/A (reply)"))
+ .addChild(TraceNode().addChild("server/java/2/B (message)").addChild(e3).addChild("server/java/2/B (reply)")).setStrict(false);
+ TraceNode expect = TraceNode()
+ .addChild(TraceNode().addChild("server/cpp/1/A (message)").addChild(e2).addChild("server/cpp/1/A (reply)"))
+ .addChild(TraceNode().addChild("server/java/1/A (message)").addChild(e2).addChild("server/java/1/A (reply)")).setStrict(false);
+ expect.normalize();
+
+ Receptor src;
+ Reply::UP reply;
+ SourceSession::UP ss = mb.getMessageBus().createSourceSession(src, SourceSessionParams());
+ for (int i = 0; i < 50; ++i) {
+ Message::UP msg(new SimpleMessage("test"));
+ msg->getTrace().setLevel(1);
+ ss->send(std::move(msg), "test");
+ reply = src.getReply(10);
+ if (reply.get() != NULL) {
+ reply->getTrace().getRoot().normalize();
+ // resending breaks the trace, so retry until it has expected form
+ if (!reply->hasErrors() && reply->getTrace().getRoot().encode() == expect.encode()) {
+ break;
+ }
+ }
+ std::cout << "Attempt " << i << " got errors, retrying in 1 second.." << std::endl;
+ FastOS_Thread::Sleep(1000);
+ }
+
+ EXPECT_TRUE(!reply->hasErrors());
+ EXPECT_EQUAL(reply->getTrace().getRoot().encode(), expect.encode());
+ EXPECT_TRUE(system("sh ctl.sh stop all") == 0);
+ TEST_DONE();
+}
diff --git a/messagebus/test/src/tests/trace/trace_test.sh b/messagebus/test/src/tests/trace/trace_test.sh
new file mode 100644
index 00000000000..bfb2edbf870
--- /dev/null
+++ b/messagebus/test/src/tests/trace/trace_test.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+. ../../binref/env.sh
+
+$BINREF/compilejava JavaServer.java
+
+./messagebus_test_trace_test_app
diff --git a/messagebus/test/testrun/.gitignore b/messagebus/test/testrun/.gitignore
new file mode 100644
index 00000000000..b29b0c6486c
--- /dev/null
+++ b/messagebus/test/testrun/.gitignore
@@ -0,0 +1,9 @@
+test-report.html
+test-report.html.*
+test.*.*.desc
+test.*.*.file.*
+test.*.*.files.html
+test.*.*.log
+tmp.*
+xsync.log
+/test.*.*.result