From a4410cb01952f6cf30ba44d6ee3ebdf40b044f45 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Sat, 27 Jul 2024 19:40:34 -0300 Subject: [PATCH 01/65] :sparkles: Re-export with Pharo 12 format --- .../AmqpChannel.class.st | 107 +++++++++--------- .../AmqpChannelHandler.class.st | 41 +++---- .../Ansible-Protocol-Core/AmqpCodec.class.st | 61 +++++----- .../AmqpCommand.class.st | 19 ++-- .../AmqpConnection.class.st | 79 ++++++------- .../AmqpConnectionBuilder.class.st | 29 ++--- .../AmqpConnectionParameters.class.st | 23 ++-- .../AmqpContentBodyFrame.class.st | 17 +-- .../AmqpContentHeaderFrame.class.st | 23 ++-- .../AmqpDisconnectedError.class.st | 7 +- .../Ansible-Protocol-Core/AmqpError.class.st | 7 +- .../AmqpExchangeDeclareBuilder.class.st | 17 +-- .../Ansible-Protocol-Core/AmqpFrame.class.st | 23 ++-- .../AmqpHeartbeatFrame.class.st | 11 +- .../AmqpHeartbeatSender.class.st | 19 ++-- .../AmqpMethodFrame.class.st | 19 ++-- .../AmqpPlainCredentials.class.st | 17 +-- .../AmqpProtocolHeaderFrame.class.st | 11 +- .../AmqpProtocolMismatchError.class.st | 11 +- .../AmqpProtocolSyntaxError.class.st | 7 +- .../AmqpQueueDeclareBuilder.class.st | 23 ++-- .../AmqpResourceError.class.st | 7 +- .../FailedSocketConnection.class.st | 13 ++- .../SocketConnectionStatus.class.st | 9 +- .../SuccesfulSocketConnection.class.st | 9 +- source/Ansible-Protocol-Core/package.st | 2 +- .../AMQPRabbitTest.class.st | 15 +-- .../Amqp091RabbitMQExtensionsTest.class.st | 11 +- .../RabbitMQTextReverser.class.st | 17 +-- .../RabbitMQTextReverserTest.class.st | 25 ++-- source/Ansible-RabbitMQ-Tests/package.st | 2 +- source/Ansible-RabbitMQ/Amqp091.extension.st | 6 +- .../Amqp091BasicNack.class.st | 31 ++--- .../Amqp091ConfirmSelect.class.st | 21 ++-- .../Amqp091ConfirmSelectOk.class.st | 17 +-- .../Ansible-RabbitMQ/AmqpChannel.extension.st | 4 +- .../Ansible-RabbitMQ/RabbitMQClient.class.st | 33 +++--- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 15 +-- source/Ansible-RabbitMQ/package.st | 2 +- .../BaselineOfAnsible.class.st | 21 ++-- source/BaselineOfAnsible/package.st | 2 +- 41 files changed, 434 insertions(+), 399 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpChannel.class.st b/source/Ansible-Protocol-Core/AmqpChannel.class.st index bc64d581..6cb6cf49 100644 --- a/source/Ansible-Protocol-Core/AmqpChannel.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannel.class.st @@ -1,27 +1,28 @@ Class { - #name : #AmqpChannel, - #superclass : #Object, + #name : 'AmqpChannel', + #superclass : 'Object', #instVars : [ 'handler', 'callbacks', 'protocolVersion' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } AmqpChannel class >> using: anAmqpChannelHandler [ ^self new initializeUsing: anAmqpChannelHandler ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> basicAck: anInteger [ - self basicAck: anInteger multiple: false + self basicAck: anInteger multiple: false ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> basicAck: anInteger multiple: aBoolean [ self @@ -31,13 +32,13 @@ AmqpChannel >> basicAck: anInteger multiple: aBoolean [ multiple: aBoolean ) ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> basicGet: aString [ ^ self basicGet: aString noAck: false ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> basicGet: aString noAck: aBoolean [ "If noAck is set the server does not expect acknowledgements for messages. That is, when a message is delivered to the client the server assumes the delivery will succeed and immediately dequeues it. This functionality may increase performance but at the cost of reliability. Messages can get lost if a client dies before they are delivered to the application." @@ -56,7 +57,7 @@ AmqpChannel >> basicGet: aString noAck: aBoolean [ ^ result ] -{ #category : #'AMQP sending messages' } +{ #category : 'AMQP sending messages' } AmqpChannel >> basicPublish: aByteArray exchange: exchangeName routingKey: routingKey [ ^ self @@ -66,7 +67,7 @@ AmqpChannel >> basicPublish: aByteArray exchange: exchangeName routingKey: routi properties: protocolVersion basicPropertiesClass new ] -{ #category : #'AMQP sending messages' } +{ #category : 'AMQP sending messages' } AmqpChannel >> basicPublish: aByteArray exchange: exchangeName routingKey: routingKey properties: aBasicProperties [ ^ self @@ -78,7 +79,7 @@ AmqpChannel >> basicPublish: aByteArray exchange: exchangeName routingKey: routi immediate: false ] -{ #category : #'AMQP sending messages' } +{ #category : 'AMQP sending messages' } AmqpChannel >> basicPublish: aByteArray exchange: exchangeName routingKey: routingKey properties: aBasicProperties mandatory: mandatory immediate: immediate [ handler connection @@ -93,19 +94,19 @@ AmqpChannel >> basicPublish: aByteArray exchange: exchangeName routingKey: routi onChannel: handler channelNumber ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> basicRecover [ ^ self basicRecover: false ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> basicRecover: shouldRequeue [ ^ self rpc: ( protocolVersion basicRecoverMethod new requeue: shouldRequeue ) ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> basicReject: anInteger requeue: aBoolean [ self @@ -115,14 +116,14 @@ AmqpChannel >> basicReject: anInteger requeue: aBoolean [ requeue: aBoolean ) ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> cancelConsumer: consumerTag [ self rpc: ( protocolVersion basicCancelMethod new consumerTag: consumerTag ). callbacks removeKey: consumerTag ifAbsent: [ ] ] -{ #category : #'AMQP closing' } +{ #category : 'AMQP closing' } AmqpChannel >> close [ | c | @@ -138,20 +139,20 @@ AmqpChannel >> close [ ] ] -{ #category : #'AMQP closing' } +{ #category : 'AMQP closing' } AmqpChannel >> closeReason [ ^ handler closeReason ] -{ #category : #'Ansible receiving messages' } +{ #category : 'Ansible receiving messages' } AmqpChannel >> consumeFrom: aQueueName applying: aBlock [ ^ self consumeFrom: aQueueName callback: aBlock selector: #value: ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol [ ^ self @@ -163,7 +164,7 @@ AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol [ consumerTag: '' ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol noAck: noAck [ ^ self @@ -175,7 +176,7 @@ AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol noAck: no consumerTag: '' ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol noAck: noAck exclusive: exclusive consumerTag: aString [ | result | @@ -191,7 +192,7 @@ AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol noAck: no ^ result method consumerTag ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol noAck: noAck exclusive: exclusive consumerTag: aString arguments: aDictionary [ | result | @@ -208,7 +209,7 @@ AmqpChannel >> consumeFrom: queue callback: anObject selector: aSymbol noAck: no ^ result method consumerTag ] -{ #category : #'Ansible exchange management' } +{ #category : 'Ansible exchange management' } AmqpChannel >> declareExchangeNamed: aName of: aType applying: aBlock [ | builder | @@ -217,7 +218,7 @@ AmqpChannel >> declareExchangeNamed: aName of: aType applying: aBlock [ ^ self rpc: ( builder buildApplying: aBlock ) ] -{ #category : #'Ansible queue building' } +{ #category : 'Ansible queue building' } AmqpChannel >> declareQueueApplying: aBlock [ | builder | @@ -226,7 +227,7 @@ AmqpChannel >> declareQueueApplying: aBlock [ ^ self rpc: ( builder buildApplying: aBlock ) ] -{ #category : #'AMQP exchange management' } +{ #category : 'AMQP exchange management' } AmqpChannel >> exchangeDeclare: exchangeName type: typeString [ ^ self @@ -238,7 +239,7 @@ AmqpChannel >> exchangeDeclare: exchangeName type: typeString [ arguments: nil ] -{ #category : #'AMQP exchange management' } +{ #category : 'AMQP exchange management' } AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable [ ^ self @@ -250,7 +251,7 @@ AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable [ arguments: nil ] -{ #category : #'AMQP exchange management' } +{ #category : 'AMQP exchange management' } AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable autoDelete: autoDelete passive: passive [ ^ self @@ -262,7 +263,7 @@ AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable a arguments: nil ] -{ #category : #'AMQP exchange management' } +{ #category : 'AMQP exchange management' } AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable autoDelete: autoDelete passive: passive arguments: aDictionary [ ^ self @@ -276,13 +277,13 @@ AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable a arguments: aDictionary ) ] -{ #category : #'AMQP exchange management' } +{ #category : 'AMQP exchange management' } AmqpChannel >> exchangeDelete: exchangeName [ ^ self exchangeDelete: exchangeName ifUnused: false ] -{ #category : #'AMQP exchange management' } +{ #category : 'AMQP exchange management' } AmqpChannel >> exchangeDelete: exchangeName ifUnused: aBoolean [ ^ self @@ -292,7 +293,7 @@ AmqpChannel >> exchangeDelete: exchangeName ifUnused: aBoolean [ ifUnused: aBoolean ) ] -{ #category : #'AMQP server-generated events' } +{ #category : 'AMQP server-generated events' } AmqpChannel >> handleBasicDeliver: cmd [ | a | @@ -301,7 +302,7 @@ AmqpChannel >> handleBasicDeliver: cmd [ a key perform: a value with: cmd ] -{ #category : #'AMQP server-generated events' } +{ #category : 'AMQP server-generated events' } AmqpChannel >> handleChannelClose: cmd [ handler internalClose: cmd method. @@ -310,7 +311,7 @@ AmqpChannel >> handleChannelClose: cmd [ onChannel: handler channelNumber "TODO: notify callbacks of closure" ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpChannel >> initializeUsing: aChannelHandler [ handler := aChannelHandler. @@ -322,20 +323,20 @@ AmqpChannel >> initializeUsing: aChannelHandler [ self rpc: protocolVersion channelOpenMethod new ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpChannel >> mapEventHandlers [ handler mapEvent: protocolVersion channelCloseMethod to: self selector: #handleChannelClose:. handler mapEvent: protocolVersion basicDeliverMethod to: self selector: #handleBasicDeliver: ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> prefetchCount: prefetchCount [ ^ self prefetchCount: prefetchCount prefetchSize: 0 global: false ] -{ #category : #'AMQP receiving messages' } +{ #category : 'AMQP receiving messages' } AmqpChannel >> prefetchCount: prefetchCount prefetchSize: prefetchSize global: aBoolean [ ^ self @@ -346,13 +347,13 @@ AmqpChannel >> prefetchCount: prefetchCount prefetchSize: prefetchSize global: a global: aBoolean ) ] -{ #category : #'AMQP private' } +{ #category : 'AMQP private' } AmqpChannel >> protocolClass [ ^ protocolVersion ] -{ #category : #'AMQP binding management' } +{ #category : 'AMQP binding management' } AmqpChannel >> queueBind: queue exchange: exchange routingKey: routingKey [ ^ self @@ -362,7 +363,7 @@ AmqpChannel >> queueBind: queue exchange: exchange routingKey: routingKey [ arguments: nil ] -{ #category : #'AMQP binding management' } +{ #category : 'AMQP binding management' } AmqpChannel >> queueBind: queue exchange: exchange routingKey: routingKey arguments: aDictionary [ ^ self @@ -374,7 +375,7 @@ AmqpChannel >> queueBind: queue exchange: exchange routingKey: routingKey argume arguments: aDictionary ) ] -{ #category : #'AMQP queue management' } +{ #category : 'AMQP queue management' } AmqpChannel >> queueDeclare: queueName [ ^ self @@ -386,7 +387,7 @@ AmqpChannel >> queueDeclare: queueName [ arguments: nil ] -{ #category : #'AMQP queue management' } +{ #category : 'AMQP queue management' } AmqpChannel >> queueDeclare: queueName durable: durable [ ^ self @@ -398,7 +399,7 @@ AmqpChannel >> queueDeclare: queueName durable: durable [ arguments: nil ] -{ #category : #'AMQP queue management' } +{ #category : 'AMQP queue management' } AmqpChannel >> queueDeclare: queueName durable: durable exclusive: exclusive autoDelete: autoDelete passive: passive arguments: aDictionary [ ^ self @@ -412,13 +413,13 @@ AmqpChannel >> queueDeclare: queueName durable: durable exclusive: exclusive aut arguments: aDictionary ) ] -{ #category : #'AMQP queue management' } +{ #category : 'AMQP queue management' } AmqpChannel >> queueDelete: queueName [ ^ self queueDelete: queueName ifUnused: false ifEmpty: false ] -{ #category : #'AMQP queue management' } +{ #category : 'AMQP queue management' } AmqpChannel >> queueDelete: queueName ifUnused: ifUnused ifEmpty: ifEmpty [ ^ self @@ -429,13 +430,13 @@ AmqpChannel >> queueDelete: queueName ifUnused: ifUnused ifEmpty: ifEmpty [ ifEmpty: ifEmpty ) ] -{ #category : #'AMQP queue management' } +{ #category : 'AMQP queue management' } AmqpChannel >> queuePurge: queue [ ^ self rpc: ( protocolVersion queuePurgeMethod new queue: queue ) ] -{ #category : #'AMQP binding management' } +{ #category : 'AMQP binding management' } AmqpChannel >> queueUnbind: queue exchange: exchange routingKey: routingKey [ ^ self @@ -445,7 +446,7 @@ AmqpChannel >> queueUnbind: queue exchange: exchange routingKey: routingKey [ arguments: nil ] -{ #category : #'AMQP binding management' } +{ #category : 'AMQP binding management' } AmqpChannel >> queueUnbind: queue exchange: exchange routingKey: routingKey arguments: aDictionary [ ^ self @@ -457,31 +458,31 @@ AmqpChannel >> queueUnbind: queue exchange: exchange routingKey: routingKey argu arguments: aDictionary ) ] -{ #category : #'AMQP private' } +{ #category : 'AMQP private' } AmqpChannel >> rpc: requestMethod [ ^ handler rpc: requestMethod ] -{ #category : #'AMQP local transactions' } +{ #category : 'AMQP local transactions' } AmqpChannel >> txCommit [ ^ self rpc: protocolVersion txCommitMethod new ] -{ #category : #'AMQP local transactions' } +{ #category : 'AMQP local transactions' } AmqpChannel >> txRollback [ ^ self rpc: protocolVersion txRollbackMethod new ] -{ #category : #'AMQP local transactions' } +{ #category : 'AMQP local transactions' } AmqpChannel >> txSelect [ ^ self rpc: protocolVersion txSelectMethod new ] -{ #category : #'AMQP private' } +{ #category : 'AMQP private' } AmqpChannel >> update: aSymbol [ aSymbol == #channelHandlerClosed diff --git a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st index 68e572d6..da05eb17 100644 --- a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st @@ -1,6 +1,6 @@ Class { - #name : #AmqpChannelHandler, - #superclass : #Object, + #name : 'AmqpChannelHandler', + #superclass : 'Object', #instVars : [ 'connection', 'channelNumber', @@ -12,16 +12,17 @@ Class { 'remainingBytes', 'bodyPieces' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpChannelHandler >> channelNumber [ ^ channelNumber ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpChannelHandler >> checkBodyCompletion [ remainingBytes > 0 @@ -33,19 +34,19 @@ AmqpChannelHandler >> checkBodyCompletion [ ] ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpChannelHandler >> closeReason [ ^ closeReason ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpChannelHandler >> connection [ ^ connection ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpChannelHandler >> connection: anObject [ connection := anObject. @@ -53,7 +54,7 @@ AmqpChannelHandler >> connection: anObject [ connection setChannel: channelNumber to: self ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> dispatchCommand [ ( asyncMap includesKey: currentCommand method class ) @@ -66,14 +67,14 @@ AmqpChannelHandler >> dispatchCommand [ self resetState ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> ensureOpen [ closeReason notNil ifTrue: [ AmqpDisconnectedError signal: 'Channel closed' ] ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> handleBodyFrame: frame [ ( frame isKindOf: AmqpContentBodyFrame ) @@ -83,13 +84,13 @@ AmqpChannelHandler >> handleBodyFrame: frame [ self checkBodyCompletion ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> handleFrame: frame [ self perform: state with: frame ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> handleMethodFrame: frame [ ( frame isKindOf: AmqpMethodFrame ) @@ -101,7 +102,7 @@ AmqpChannelHandler >> handleMethodFrame: frame [ ifFalse: [ self dispatchCommand ] ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> handlePropertiesFrame: frame [ ( frame isKindOf: AmqpContentHeaderFrame ) @@ -112,7 +113,7 @@ AmqpChannelHandler >> handlePropertiesFrame: frame [ self checkBodyCompletion ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpChannelHandler >> initialize [ super initialize. @@ -122,7 +123,7 @@ AmqpChannelHandler >> initialize [ self resetState ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> internalClose: method [ closeReason @@ -132,13 +133,13 @@ AmqpChannelHandler >> internalClose: method [ ] ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> mapEvent: evtClass to: receiver selector: aSymbol [ asyncMap at: evtClass put: receiver -> aSymbol ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> resetState [ state := #handleMethodFrame:. @@ -147,13 +148,13 @@ AmqpChannelHandler >> resetState [ bodyPieces := nil ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> rpc: requestMethod [ ^ connection rpc: requestMethod onChannel: channelNumber ] -{ #category : #handling } +{ #category : 'handling' } AmqpChannelHandler >> waitForReply: acceptableReplies [ | i | diff --git a/source/Ansible-Protocol-Core/AmqpCodec.class.st b/source/Ansible-Protocol-Core/AmqpCodec.class.st index 042be6b7..b357ba9d 100644 --- a/source/Ansible-Protocol-Core/AmqpCodec.class.st +++ b/source/Ansible-Protocol-Core/AmqpCodec.class.st @@ -1,43 +1,44 @@ Class { - #name : #AmqpCodec, - #superclass : #Object, + #name : 'AmqpCodec', + #superclass : 'Object', #instVars : [ 'stream' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } AmqpCodec class >> on: aStream [ ^ self new initializeOn: aStream ] -{ #category : #streaming } +{ #category : 'streaming' } AmqpCodec >> flush [ stream flush ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpCodec >> initializeOn: aStream [ stream := aStream ] -{ #category : #streaming } +{ #category : 'streaming' } AmqpCodec >> next: length [ ^ stream next: length ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextBoolean [ ^ self nextOctet bitAnd: 1 ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextLong [ | v | @@ -49,7 +50,7 @@ AmqpCodec >> nextLong [ ^ v ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextLongPut: v [ v ifNil: [ ^ self nextLongPut: 0 ]. @@ -57,7 +58,7 @@ AmqpCodec >> nextLongPut: v [ ^ v ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextLonglong [ | v | @@ -73,7 +74,7 @@ AmqpCodec >> nextLonglong [ ^ v ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextLonglongPut: v [ v ifNil: [ ^ self nextLonglongPut: 0 ]. @@ -81,19 +82,19 @@ AmqpCodec >> nextLonglongPut: v [ ^ v ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextLongstr [ ^ self nextLongstrBytes asString ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextLongstrBytes [ ^ stream next: self nextLong ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextLongstrPut: v [ v @@ -105,26 +106,26 @@ AmqpCodec >> nextLongstrPut: v [ ^ v ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextOctet [ ^ stream next ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextOctetPut: v [ v ifNil: [ ^ self nextOctetPut: 0 ]. ^ stream nextPut: v ] -{ #category : #streaming } +{ #category : 'streaming' } AmqpCodec >> nextPutAll: aCollection [ stream nextPutAll: aCollection ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextShort [ | v | @@ -134,7 +135,7 @@ AmqpCodec >> nextShort [ ^ v ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextShortPut: v [ v ifNil: [ ^ self nextShortPut: 0 ]. @@ -142,13 +143,13 @@ AmqpCodec >> nextShortPut: v [ ^ v ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextShortstr [ ^ ( stream next: self nextOctet ) asString ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextShortstrPut: v [ v @@ -160,7 +161,7 @@ AmqpCodec >> nextShortstrPut: v [ ^ v ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextTable [ | buffer table key type value | @@ -179,7 +180,7 @@ AmqpCodec >> nextTable [ ^ table ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextTablePut: table [ | buffer | @@ -201,7 +202,7 @@ AmqpCodec >> nextTablePut: table [ ^ table ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextTableValue: type [ type = $S asciiValue @@ -224,7 +225,7 @@ AmqpCodec >> nextTableValue: type [ AmqpProtocolSyntaxError signal: 'Unsupported AMQP table field type' ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextTableValuePut: val [ ( val isKindOf: ScaledDecimal ) @@ -256,13 +257,13 @@ AmqpCodec >> nextTableValuePut: val [ AmqpProtocolSyntaxError signal: 'Unsupported AMQP table field value' ] -{ #category : #decoding } +{ #category : 'decoding' } AmqpCodec >> nextTimestamp [ ^ ( DateAndTime year: 1970 day: 1 ) + self nextLonglong seconds ] -{ #category : #encoding } +{ #category : 'encoding' } AmqpCodec >> nextTimestampPut: v [ v @@ -273,13 +274,13 @@ AmqpCodec >> nextTimestampPut: v [ ^ v ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpCodec >> stream [ ^ stream ] -{ #category : #streaming } +{ #category : 'streaming' } AmqpCodec >> withStream: aStream during: aBlock [ | saved | diff --git a/source/Ansible-Protocol-Core/AmqpCommand.class.st b/source/Ansible-Protocol-Core/AmqpCommand.class.st index 32a4158e..2ba82623 100644 --- a/source/Ansible-Protocol-Core/AmqpCommand.class.st +++ b/source/Ansible-Protocol-Core/AmqpCommand.class.st @@ -1,45 +1,46 @@ Class { - #name : #AmqpCommand, - #superclass : #Object, + #name : 'AmqpCommand', + #superclass : 'Object', #instVars : [ 'method', 'properties', 'body' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpCommand >> body [ ^ body ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpCommand >> body: anObject [ body := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpCommand >> method [ ^ method ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpCommand >> method: anObject [ method := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpCommand >> properties [ ^ properties ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpCommand >> properties: anObject [ properties := anObject diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index dadaff8d..5388ebbc 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -1,6 +1,6 @@ Class { - #name : #AmqpConnection, - #superclass : #Object, + #name : 'AmqpConnection', + #superclass : 'Object', #instVars : [ 'socket', 'codec', @@ -17,10 +17,11 @@ Class { 'socketConnectionStatus', 'portNumber' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } AmqpConnection class >> to: aHostname over: aPort using: aProtocolVersion with: connectionCredentials parameterizedBy: connectionParameters [ ^ self new @@ -31,7 +32,7 @@ AmqpConnection class >> to: aHostname over: aPort using: aProtocolVersion with: parameterizedBy: connectionParameters ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> close [ | connectionClose | @@ -51,19 +52,19 @@ AmqpConnection >> close [ socket close ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> closeReason [ ^ closeReason ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnection >> codec [ ^ codec ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> createChannel [ | handler | @@ -73,27 +74,27 @@ AmqpConnection >> createChannel [ ^ AmqpChannel using: handler ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnection >> credentials [ ^ credentials ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> ensureChannel: channelNumber [ self ensureOpen. ^ ( channels at: channelNumber ) ensureOpen ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> ensureOpen [ closeReason notNil ifTrue: [ AmqpDisconnectedError signal: 'Connection closed' ] ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> handleConnectionClose: cmd [ self internalClose: cmd method. @@ -102,7 +103,7 @@ AmqpConnection >> handleConnectionClose: cmd [ socket close ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> hardClose [ | connectionClose | @@ -118,7 +119,7 @@ AmqpConnection >> hardClose [ socket close ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpConnection >> initialize [ super initialize. @@ -129,13 +130,13 @@ AmqpConnection >> initialize [ nextChannel := 0 ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpConnection >> initializeHeartbeatSender [ heartbeatSender := AmqpHeartbeatSender keepingOpen: self ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpConnection >> initializeSocketConnection [ socket := Socket newTCP. @@ -150,7 +151,7 @@ AmqpConnection >> initializeSocketConnection [ ] ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpConnection >> initializeTo: aHostname over: aPort using: aProtocolVersion with: connectionCredentials parameterizedBy: connectionParameters [ protocolClass := aProtocolVersion. @@ -162,7 +163,7 @@ AmqpConnection >> initializeTo: aHostname over: aPort using: aProtocolVersion wi self initializeHeartbeatSender ] -{ #category : #'private-opening' } +{ #category : 'private-opening' } AmqpConnection >> installChannel0 [ | channel | @@ -171,7 +172,7 @@ AmqpConnection >> installChannel0 [ channel mapEvent: self protocolClass connectionCloseMethod to: self selector: #handleConnectionClose: ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> internalClose: method [ closeReason @@ -181,7 +182,7 @@ AmqpConnection >> internalClose: method [ ] ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> mainLoopCycle [ | frame | @@ -194,7 +195,7 @@ AmqpConnection >> mainLoopCycle [ ifFalse: [ (channels at: frame channelNumber) handleFrame: frame ] ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> nextChannel [ | tries limit | @@ -214,7 +215,7 @@ AmqpConnection >> nextChannel [ ^ nextChannel ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> nextFrame [ | typeCode channel length frameType frame | @@ -237,7 +238,7 @@ AmqpConnection >> nextFrame [ ] repeat ] -{ #category : #opening } +{ #category : 'opening' } AmqpConnection >> open [ codec := AmqpCodec @@ -256,7 +257,7 @@ AmqpConnection >> open [ heartbeatSender startBeatingEvery: parameters heartbeat ] -{ #category : #'private-opening' } +{ #category : 'private-opening' } AmqpConnection >> openConnection [ @@ -269,7 +270,7 @@ AmqpConnection >> openConnection [ isOpen := true. ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> processAsyncEvents [ [ self ensureOpen. @@ -277,19 +278,19 @@ AmqpConnection >> processAsyncEvents [ ] whileTrue: [ self mainLoopCycle ] ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> protocolClass [ ^ protocolClass ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> resetChannel: channelNumber [ channels removeKey: channelNumber ifAbsent: [ ] ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> rpc: requestMethod onChannel: channelNumber [ | ch | @@ -299,7 +300,7 @@ AmqpConnection >> rpc: requestMethod onChannel: channelNumber [ ^ ch waitForReply: requestMethod acceptableResponseClasses ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> sendBodyFrameContaining: aByteArray startingAt: aStartingPosition onChannel: aChannelNumber [ | length | @@ -314,14 +315,14 @@ AmqpConnection >> sendBodyFrameContaining: aByteArray startingAt: aStartingPosit ^ aStartingPosition + length ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> sendFrame: aFrame [ aFrame encodeOn: codec. codec nextOctetPut: self protocolClass frameEnd ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> sendMethod: aMethod onChannel: aChannelNumber [ self @@ -334,7 +335,7 @@ AmqpConnection >> sendMethod: aMethod onChannel: aChannelNumber [ codec flush ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> sendMethod: aMethod properties: aProperties body: aByteArray onChannel: channelNumber [ | pos | @@ -361,20 +362,20 @@ AmqpConnection >> sendMethod: aMethod properties: aProperties body: aByteArray o codec flush ] -{ #category : #'private-opening' } +{ #category : 'private-opening' } AmqpConnection >> sendProtocolHeader [ codec nextPutAll: self protocolClass protocolHeader. codec flush ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnection >> setChannel: aChannelNumber to: aChannelHandler [ channels at: aChannelNumber put: aChannelHandler ] -{ #category : #'private-opening' } +{ #category : 'private-opening' } AmqpConnection >> startConnection [ | response | @@ -396,7 +397,7 @@ AmqpConnection >> startConnection [ credentials := nil ] -{ #category : #'private-opening' } +{ #category : 'private-opening' } AmqpConnection >> tuneConnection [ parameters applyServerSettings: self nextFrame method. @@ -409,13 +410,13 @@ AmqpConnection >> tuneConnection [ onChannel: 0 ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnection >> virtualHost [ ^ virtualHost ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> waitForEvent [ self ensureOpen. @@ -425,7 +426,7 @@ AmqpConnection >> waitForEvent [ ^ self processAsyncEvents ] -{ #category : #'connection-handling' } +{ #category : 'connection-handling' } AmqpConnection >> whenConnected: aBlock whenNot: anotherBlock [ ^socketConnectionStatus whenConnected: aBlock whenNot: anotherBlock diff --git a/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st b/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st index 195792b6..8cbf420e 100644 --- a/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st @@ -1,6 +1,6 @@ Class { - #name : #AmqpConnectionBuilder, - #superclass : #Object, + #name : 'AmqpConnectionBuilder', + #superclass : 'Object', #instVars : [ 'parameters', 'hostname', @@ -9,16 +9,17 @@ Class { 'portNumber', 'protocolClass' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } AmqpConnectionBuilder class >> forProtocol: aProtocolClass [ ^self new initializeForProtocol: aProtocolClass ] -{ #category : #building } +{ #category : 'building' } AmqpConnectionBuilder >> build [ protocolClass ifNil: [ Error signal: 'Protocol must be configured' ]. @@ -31,7 +32,7 @@ AmqpConnectionBuilder >> build [ parameterizedBy: parameters ] -{ #category : #'private-accessing' } +{ #category : 'private-accessing' } AmqpConnectionBuilder >> credentials [ ^ AmqpPlainCredentials new @@ -39,19 +40,19 @@ AmqpConnectionBuilder >> credentials [ password: password ] -{ #category : #'private-accessing' } +{ #category : 'private-accessing' } AmqpConnectionBuilder >> defaultPortNumber [ ^ protocolClass portNumber ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpConnectionBuilder >> hostname: aHostname [ hostname := aHostname ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpConnectionBuilder >> initialize [ super initialize. @@ -65,32 +66,32 @@ AmqpConnectionBuilder >> initialize [ heartbeat: 0 ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpConnectionBuilder >> initializeForProtocol: aProtocolClass [ protocolClass := aProtocolClass. self portNumber: self defaultPortNumber ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpConnectionBuilder >> password: aPassword [ password := aPassword ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpConnectionBuilder >> portNumber: aPortNumber [ portNumber := aPortNumber ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpConnectionBuilder >> protocol: aProtocol [ protocolClass := aProtocol ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpConnectionBuilder >> username: anUsername [ username := anUsername diff --git a/source/Ansible-Protocol-Core/AmqpConnectionParameters.class.st b/source/Ansible-Protocol-Core/AmqpConnectionParameters.class.st index b794e0e0..545abb96 100644 --- a/source/Ansible-Protocol-Core/AmqpConnectionParameters.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnectionParameters.class.st @@ -1,15 +1,16 @@ Class { - #name : #AmqpConnectionParameters, - #superclass : #Object, + #name : 'AmqpConnectionParameters', + #superclass : 'Object', #instVars : [ 'channelMax', 'frameMax', 'heartbeat' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> applyServerSettings: aTune [ channelMax := self combine: channelMax with: aTune channelMax. @@ -17,19 +18,19 @@ AmqpConnectionParameters >> applyServerSettings: aTune [ heartbeat := self combine: heartbeat with: aTune heartbeat ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> channelMax [ ^ channelMax ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> channelMax: anObject [ channelMax := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> combine: v1 with: v2 [ v1 = 0 @@ -39,25 +40,25 @@ AmqpConnectionParameters >> combine: v1 with: v2 [ ^ v1 min: v2 ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> frameMax [ ^ frameMax ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> frameMax: anObject [ frameMax := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> heartbeat [ ^ heartbeat ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpConnectionParameters >> heartbeat: anObject [ heartbeat := anObject diff --git a/source/Ansible-Protocol-Core/AmqpContentBodyFrame.class.st b/source/Ansible-Protocol-Core/AmqpContentBodyFrame.class.st index 7187ccd2..296a53f9 100644 --- a/source/Ansible-Protocol-Core/AmqpContentBodyFrame.class.st +++ b/source/Ansible-Protocol-Core/AmqpContentBodyFrame.class.st @@ -1,13 +1,14 @@ Class { - #name : #AmqpContentBodyFrame, - #superclass : #AmqpFrame, + #name : 'AmqpContentBodyFrame', + #superclass : 'AmqpFrame', #instVars : [ 'fragment' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentBodyFrame >> encodeBodyOn: codec [ "do nothing." @@ -15,25 +16,25 @@ AmqpContentBodyFrame >> encodeBodyOn: codec [ ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentBodyFrame >> encodedBody [ ^ fragment ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentBodyFrame >> fragment [ ^ fragment ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentBodyFrame >> fragment: anObject [ fragment := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentBodyFrame >> readFrom: connection length: length [ fragment := connection codec next: length diff --git a/source/Ansible-Protocol-Core/AmqpContentHeaderFrame.class.st b/source/Ansible-Protocol-Core/AmqpContentHeaderFrame.class.st index 5481ec57..b763a598 100644 --- a/source/Ansible-Protocol-Core/AmqpContentHeaderFrame.class.st +++ b/source/Ansible-Protocol-Core/AmqpContentHeaderFrame.class.st @@ -1,39 +1,40 @@ Class { - #name : #AmqpContentHeaderFrame, - #superclass : #AmqpFrame, + #name : 'AmqpContentHeaderFrame', + #superclass : 'AmqpFrame', #instVars : [ 'classId', 'bodySize', 'properties' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> bodySize [ ^ bodySize ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> bodySize: anObject [ bodySize := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> classId [ ^ classId ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> classId: anObject [ classId := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> encodeBodyOn: codec [ codec nextShortPut: classId. @@ -42,19 +43,19 @@ AmqpContentHeaderFrame >> encodeBodyOn: codec [ properties encodeOn: codec ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> properties [ ^ properties ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> properties: anObject [ properties := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpContentHeaderFrame >> readFrom: connection length: length [ | codec | diff --git a/source/Ansible-Protocol-Core/AmqpDisconnectedError.class.st b/source/Ansible-Protocol-Core/AmqpDisconnectedError.class.st index ceed8a1b..3d85df56 100644 --- a/source/Ansible-Protocol-Core/AmqpDisconnectedError.class.st +++ b/source/Ansible-Protocol-Core/AmqpDisconnectedError.class.st @@ -1,5 +1,6 @@ Class { - #name : #AmqpDisconnectedError, - #superclass : #AmqpError, - #category : #'Ansible-Protocol-Core' + #name : 'AmqpDisconnectedError', + #superclass : 'AmqpError', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } diff --git a/source/Ansible-Protocol-Core/AmqpError.class.st b/source/Ansible-Protocol-Core/AmqpError.class.st index 9e0739fd..b41db479 100644 --- a/source/Ansible-Protocol-Core/AmqpError.class.st +++ b/source/Ansible-Protocol-Core/AmqpError.class.st @@ -1,5 +1,6 @@ Class { - #name : #AmqpError, - #superclass : #Error, - #category : #'Ansible-Protocol-Core' + #name : 'AmqpError', + #superclass : 'Error', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } diff --git a/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st b/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st index ec6fac21..5a04baf5 100644 --- a/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st +++ b/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st @@ -1,6 +1,6 @@ Class { - #name : #AmqpExchangeDeclareBuilder, - #superclass : #Object, + #name : 'AmqpExchangeDeclareBuilder', + #superclass : 'Object', #instVars : [ 'name', 'type', @@ -9,28 +9,29 @@ Class { 'arguments', 'protocolVersion' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } AmqpExchangeDeclareBuilder class >> named: aName of: aType for: aProtocolVersion [ ^ self new initializeNamed: aName of: aType for: aProtocolVersion ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpExchangeDeclareBuilder >> beDurable [ durable := true ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpExchangeDeclareBuilder >> bePassive [ passive := true ] -{ #category : #building } +{ #category : 'building' } AmqpExchangeDeclareBuilder >> buildApplying: aBlock [ aBlock value: self. @@ -43,7 +44,7 @@ AmqpExchangeDeclareBuilder >> buildApplying: aBlock [ arguments: arguments ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpExchangeDeclareBuilder >> initializeNamed: aName of: aType for: aProtocolVersion [ protocolVersion := aProtocolVersion. diff --git a/source/Ansible-Protocol-Core/AmqpFrame.class.st b/source/Ansible-Protocol-Core/AmqpFrame.class.st index c9cad92e..6531a670 100644 --- a/source/Ansible-Protocol-Core/AmqpFrame.class.st +++ b/source/Ansible-Protocol-Core/AmqpFrame.class.st @@ -1,32 +1,33 @@ Class { - #name : #AmqpFrame, - #superclass : #Object, + #name : 'AmqpFrame', + #superclass : 'Object', #instVars : [ 'frameType', 'channelNumber' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> channelNumber [ ^ channelNumber ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> channelNumber: anObject [ channelNumber := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> encodeBodyOn: codec [ self subclassResponsibility ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> encodeOn: codec [ codec @@ -35,7 +36,7 @@ AmqpFrame >> encodeOn: codec [ nextLongstrPut: self encodedBody ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> encodedBody [ | stream codec | @@ -47,19 +48,19 @@ AmqpFrame >> encodedBody [ ^ stream contents ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> frameType [ ^ frameType ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> frameType: anObject [ frameType := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpFrame >> readFrom: connection length: length [ self subclassResponsibility diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatFrame.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatFrame.class.st index 34247a40..99cbfdd2 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatFrame.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatFrame.class.st @@ -1,10 +1,11 @@ Class { - #name : #AmqpHeartbeatFrame, - #superclass : #AmqpFrame, - #category : #'Ansible-Protocol-Core' + #name : 'AmqpHeartbeatFrame', + #superclass : 'AmqpFrame', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpHeartbeatFrame >> encodeBodyOn: codec [ "do nothing." @@ -12,7 +13,7 @@ AmqpHeartbeatFrame >> encodeBodyOn: codec [ ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpHeartbeatFrame >> readFrom: connection length: length [ length = 0 diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index 56c9c752..a82e1adf 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -1,21 +1,22 @@ Class { - #name : #AmqpHeartbeatSender, - #superclass : #Object, + #name : 'AmqpHeartbeatSender', + #superclass : 'Object', #instVars : [ 'connection', 'process', 'isStarted' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } AmqpHeartbeatSender class >> keepingOpen: anAmqpConnection [ ^ self new initializeKeepingOpen: anAmqpConnection ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpHeartbeatSender >> heartbeatFrame [ ^ AmqpHeartbeatFrame new @@ -24,14 +25,14 @@ AmqpHeartbeatSender >> heartbeatFrame [ yourself ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpHeartbeatSender >> initializeKeepingOpen: anAmqpConnection [ connection := anAmqpConnection. isStarted := false ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpHeartbeatSender >> spawnProcessSendingHeartbeatEvery: aTimeInterval [ ^ ( Process @@ -49,7 +50,7 @@ AmqpHeartbeatSender >> spawnProcessSendingHeartbeatEvery: aTimeInterval [ yourself ] -{ #category : #'startup-shutdown' } +{ #category : 'startup-shutdown' } AmqpHeartbeatSender >> startBeatingEvery: aTimeInterval [ aTimeInterval ~= 0 @@ -59,7 +60,7 @@ AmqpHeartbeatSender >> startBeatingEvery: aTimeInterval [ ] ] -{ #category : #'startup-shutdown' } +{ #category : 'startup-shutdown' } AmqpHeartbeatSender >> stop [ process terminate diff --git a/source/Ansible-Protocol-Core/AmqpMethodFrame.class.st b/source/Ansible-Protocol-Core/AmqpMethodFrame.class.st index 4fac5969..1e2c790f 100644 --- a/source/Ansible-Protocol-Core/AmqpMethodFrame.class.st +++ b/source/Ansible-Protocol-Core/AmqpMethodFrame.class.st @@ -1,45 +1,46 @@ Class { - #name : #AmqpMethodFrame, - #superclass : #AmqpFrame, + #name : 'AmqpMethodFrame', + #superclass : 'AmqpFrame', #instVars : [ 'methodId', 'method' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpMethodFrame >> encodeBodyOn: codec [ codec nextLongPut: methodId. method encodeOn: codec ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpMethodFrame >> method [ ^ method ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpMethodFrame >> method: anObject [ method := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpMethodFrame >> methodId [ ^ methodId ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpMethodFrame >> methodId: anObject [ methodId := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpMethodFrame >> readFrom: connection length: length [ | codec | diff --git a/source/Ansible-Protocol-Core/AmqpPlainCredentials.class.st b/source/Ansible-Protocol-Core/AmqpPlainCredentials.class.st index d7c4ca21..40a8aa8a 100644 --- a/source/Ansible-Protocol-Core/AmqpPlainCredentials.class.st +++ b/source/Ansible-Protocol-Core/AmqpPlainCredentials.class.st @@ -1,26 +1,27 @@ Class { - #name : #AmqpPlainCredentials, - #superclass : #Object, + #name : 'AmqpPlainCredentials', + #superclass : 'Object', #instVars : [ 'username', 'password' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpPlainCredentials >> password [ ^ password ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpPlainCredentials >> password: anObject [ password := anObject ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpPlainCredentials >> responseFor: aMethod [ | s | @@ -37,13 +38,13 @@ AmqpPlainCredentials >> responseFor: aMethod [ ^ 'PLAIN' -> s contents ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpPlainCredentials >> username [ ^ username ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpPlainCredentials >> username: anObject [ username := anObject diff --git a/source/Ansible-Protocol-Core/AmqpProtocolHeaderFrame.class.st b/source/Ansible-Protocol-Core/AmqpProtocolHeaderFrame.class.st index 73ac8904..d51046d3 100644 --- a/source/Ansible-Protocol-Core/AmqpProtocolHeaderFrame.class.st +++ b/source/Ansible-Protocol-Core/AmqpProtocolHeaderFrame.class.st @@ -1,6 +1,6 @@ Class { - #name : #AmqpProtocolHeaderFrame, - #superclass : #AmqpFrame, + #name : 'AmqpProtocolHeaderFrame', + #superclass : 'AmqpFrame', #instVars : [ 'majorVersion', 'minorVersion', @@ -8,10 +8,11 @@ Class { 'transportVersion', 'isObsoleteProtocolVariant' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpProtocolHeaderFrame >> encodeBodyOn: codec [ "do nothing." @@ -19,7 +20,7 @@ AmqpProtocolHeaderFrame >> encodeBodyOn: codec [ ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpProtocolHeaderFrame >> readFrom: connection length: length [ | codec | diff --git a/source/Ansible-Protocol-Core/AmqpProtocolMismatchError.class.st b/source/Ansible-Protocol-Core/AmqpProtocolMismatchError.class.st index a7c5ac42..b8f9916c 100644 --- a/source/Ansible-Protocol-Core/AmqpProtocolMismatchError.class.st +++ b/source/Ansible-Protocol-Core/AmqpProtocolMismatchError.class.st @@ -1,19 +1,20 @@ Class { - #name : #AmqpProtocolMismatchError, - #superclass : #AmqpError, + #name : 'AmqpProtocolMismatchError', + #superclass : 'AmqpError', #instVars : [ 'serverProtocolHeader' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #accessing } +{ #category : 'accessing' } AmqpProtocolMismatchError >> serverProtocolHeader [ ^ serverProtocolHeader ] -{ #category : #accessing } +{ #category : 'accessing' } AmqpProtocolMismatchError >> serverProtocolHeader: anObject [ serverProtocolHeader := anObject diff --git a/source/Ansible-Protocol-Core/AmqpProtocolSyntaxError.class.st b/source/Ansible-Protocol-Core/AmqpProtocolSyntaxError.class.st index c41d18cf..9082e5fc 100644 --- a/source/Ansible-Protocol-Core/AmqpProtocolSyntaxError.class.st +++ b/source/Ansible-Protocol-Core/AmqpProtocolSyntaxError.class.st @@ -1,5 +1,6 @@ Class { - #name : #AmqpProtocolSyntaxError, - #superclass : #AmqpError, - #category : #'Ansible-Protocol-Core' + #name : 'AmqpProtocolSyntaxError', + #superclass : 'AmqpError', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } diff --git a/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st b/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st index 6a53bc9f..29d41392 100644 --- a/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st +++ b/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st @@ -1,6 +1,6 @@ Class { - #name : #AmqpQueueDeclareBuilder, - #superclass : #Object, + #name : 'AmqpQueueDeclareBuilder', + #superclass : 'Object', #instVars : [ 'protocolVersion', 'queueName', @@ -10,40 +10,41 @@ Class { 'autoDelete', 'arguments' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } AmqpQueueDeclareBuilder class >> for: aProtocolVersion [ ^ self new initializeFor: aProtocolVersion ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpQueueDeclareBuilder >> autoDelete [ autoDelete := true ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpQueueDeclareBuilder >> beDurable [ durable := true ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpQueueDeclareBuilder >> beExclusive [ exclusive := true ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpQueueDeclareBuilder >> bePassive [ passive := true ] -{ #category : #building } +{ #category : 'building' } AmqpQueueDeclareBuilder >> buildApplying: aBlock [ aBlock value: self. @@ -57,7 +58,7 @@ AmqpQueueDeclareBuilder >> buildApplying: aBlock [ arguments: arguments ] -{ #category : #initialization } +{ #category : 'initialization' } AmqpQueueDeclareBuilder >> initializeFor: aProtocolVersion [ protocolVersion := aProtocolVersion. @@ -70,7 +71,7 @@ AmqpQueueDeclareBuilder >> initializeFor: aProtocolVersion [ arguments := nil ] -{ #category : #configuring } +{ #category : 'configuring' } AmqpQueueDeclareBuilder >> name: aQueueName [ queueName := aQueueName diff --git a/source/Ansible-Protocol-Core/AmqpResourceError.class.st b/source/Ansible-Protocol-Core/AmqpResourceError.class.st index 486a5e70..a01dfef0 100644 --- a/source/Ansible-Protocol-Core/AmqpResourceError.class.st +++ b/source/Ansible-Protocol-Core/AmqpResourceError.class.st @@ -1,5 +1,6 @@ Class { - #name : #AmqpResourceError, - #superclass : #AmqpError, - #category : #'Ansible-Protocol-Core' + #name : 'AmqpResourceError', + #superclass : 'AmqpError', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } diff --git a/source/Ansible-Protocol-Core/FailedSocketConnection.class.st b/source/Ansible-Protocol-Core/FailedSocketConnection.class.st index 6a6c4fa6..e6776a14 100644 --- a/source/Ansible-Protocol-Core/FailedSocketConnection.class.st +++ b/source/Ansible-Protocol-Core/FailedSocketConnection.class.st @@ -1,25 +1,26 @@ Class { - #name : #FailedSocketConnection, - #superclass : #SocketConnectionStatus, + #name : 'FailedSocketConnection', + #superclass : 'SocketConnectionStatus', #instVars : [ 'error' ], - #category : #'Ansible-Protocol-Core' + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } FailedSocketConnection class >> dueTo: anError [ ^ self new initializeDueTo: anError ] -{ #category : #initialization } +{ #category : 'initialization' } FailedSocketConnection >> initializeDueTo: anError [ error := anError ] -{ #category : #evaluating } +{ #category : 'evaluating' } FailedSocketConnection >> whenConnected: aBlock whenNot: anotherBlock [ ^anotherBlock cull: error diff --git a/source/Ansible-Protocol-Core/SocketConnectionStatus.class.st b/source/Ansible-Protocol-Core/SocketConnectionStatus.class.st index 58d55ac7..00e922d0 100644 --- a/source/Ansible-Protocol-Core/SocketConnectionStatus.class.st +++ b/source/Ansible-Protocol-Core/SocketConnectionStatus.class.st @@ -1,10 +1,11 @@ Class { - #name : #SocketConnectionStatus, - #superclass : #Object, - #category : #'Ansible-Protocol-Core' + #name : 'SocketConnectionStatus', + #superclass : 'Object', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #evaluating } +{ #category : 'evaluating' } SocketConnectionStatus >> whenConnected: aBlock whenNot: anotherBlock [ self subclassResponsibility diff --git a/source/Ansible-Protocol-Core/SuccesfulSocketConnection.class.st b/source/Ansible-Protocol-Core/SuccesfulSocketConnection.class.st index 66de95f4..e521ae1d 100644 --- a/source/Ansible-Protocol-Core/SuccesfulSocketConnection.class.st +++ b/source/Ansible-Protocol-Core/SuccesfulSocketConnection.class.st @@ -1,10 +1,11 @@ Class { - #name : #SuccesfulSocketConnection, - #superclass : #SocketConnectionStatus, - #category : #'Ansible-Protocol-Core' + #name : 'SuccesfulSocketConnection', + #superclass : 'SocketConnectionStatus', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' } -{ #category : #evaluating } +{ #category : 'evaluating' } SuccesfulSocketConnection >> whenConnected: aBlock whenNot: anotherBlock [ ^aBlock value diff --git a/source/Ansible-Protocol-Core/package.st b/source/Ansible-Protocol-Core/package.st index 62837870..1c34ba88 100644 --- a/source/Ansible-Protocol-Core/package.st +++ b/source/Ansible-Protocol-Core/package.st @@ -1 +1 @@ -Package { #name : #'Ansible-Protocol-Core' } +Package { #name : 'Ansible-Protocol-Core' } diff --git a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st b/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st index 5e30954b..af743de5 100644 --- a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st @@ -1,11 +1,12 @@ Class { - #name : #AMQPRabbitTest, - #superclass : #TestCase, - #category : #'Ansible-RabbitMQ-Tests' + #name : 'AMQPRabbitTest', + #superclass : 'TestCase', + #category : 'Ansible-RabbitMQ-Tests', + #package : 'Ansible-RabbitMQ-Tests' } -{ #category : #private } -AMQPRabbitTest >> publish: aMessageCollection onQueueNamed: aQueueName [ +{ #category : 'private' } +AMQPRabbitTest >> publish: aMessageCollection onQueueNamed: aQueueName [ self withLocalhostConnectionDo: [ :connection | | channel queue confirmationWasReceived | @@ -35,7 +36,7 @@ AMQPRabbitTest >> publish: aMessageCollection onQueueNamed: aQueueName [ ] ] -{ #category : #tests } +{ #category : 'tests' } AMQPRabbitTest >> testBasicConsumeUsingPublisherConfirmation [ | channel queue | @@ -71,7 +72,7 @@ AMQPRabbitTest >> testBasicConsumeUsingPublisherConfirmation [ ] ] -{ #category : #private } +{ #category : 'private' } AMQPRabbitTest >> withLocalhostConnectionDo: block [ | connection | diff --git a/source/Ansible-RabbitMQ-Tests/Amqp091RabbitMQExtensionsTest.class.st b/source/Ansible-RabbitMQ-Tests/Amqp091RabbitMQExtensionsTest.class.st index a9f6e085..46e77f60 100644 --- a/source/Ansible-RabbitMQ-Tests/Amqp091RabbitMQExtensionsTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/Amqp091RabbitMQExtensionsTest.class.st @@ -1,16 +1,17 @@ Class { - #name : #Amqp091RabbitMQExtensionsTest, - #superclass : #TestCase, - #category : #'Ansible-RabbitMQ-Tests' + #name : 'Amqp091RabbitMQExtensionsTest', + #superclass : 'TestCase', + #category : 'Ansible-RabbitMQ-Tests', + #package : 'Ansible-RabbitMQ-Tests' } -{ #category : #tests } +{ #category : 'tests' } Amqp091RabbitMQExtensionsTest >> testBasicNackMethod [ self assert: Amqp091 basicNackMethod equals: Amqp091BasicNack ] -{ #category : #tests } +{ #category : 'tests' } Amqp091RabbitMQExtensionsTest >> testConfirmSelectMethod [ self assert: Amqp091 confirmSelectMethod equals: Amqp091ConfirmSelect diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index 8d60ed4c..4297922f 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -1,38 +1,39 @@ Class { - #name : #RabbitMQTextReverser, - #superclass : #RabbitMQWorker, + #name : 'RabbitMQTextReverser', + #superclass : 'RabbitMQWorker', #instVars : [ 'testCase' ], - #category : #'Ansible-RabbitMQ-Tests' + #category : 'Ansible-RabbitMQ-Tests', + #package : 'Ansible-RabbitMQ-Tests' } -{ #category : #'instance creation' } +{ #category : 'instance creation' } RabbitMQTextReverser class >> workingWith: aTestCase [ ^self new initializeWorkingWith: aTestCase ] -{ #category : #private } +{ #category : 'private' } RabbitMQTextReverser >> configureConnection: builder [ ] -{ #category : #initialization } +{ #category : 'initialization' } RabbitMQTextReverser >> initializeWorkingWith: aTestCase [ testCase := aTestCase. self initializeConnection ] -{ #category : #private } +{ #category : 'private' } RabbitMQTextReverser >> process: payload [ testCase storeText: payload utf8Decoded reversed ] -{ #category : #accessing } +{ #category : 'accessing' } RabbitMQTextReverser >> queueName [ ^ testCase queueName diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st index 3e6354ed..01897280 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st @@ -2,16 +2,17 @@ A RabbitMQTextReverserTest is a test class for testing the behavior of RabbitMQTextReverser " Class { - #name : #RabbitMQTextReverserTest, - #superclass : #TestCase, + #name : 'RabbitMQTextReverserTest', + #superclass : 'TestCase', #instVars : [ 'reversedTexts', 'workerProcess' ], - #category : #'Ansible-RabbitMQ-Tests' + #category : 'Ansible-RabbitMQ-Tests', + #package : 'Ansible-RabbitMQ-Tests' } -{ #category : #private } +{ #category : 'private' } RabbitMQTextReverserTest >> publish: text onQueueNamed: aQueueName [ self withLocalhostConnectionDo: [ :connection | @@ -27,13 +28,13 @@ RabbitMQTextReverserTest >> publish: text onQueueNamed: aQueueName [ ] ] -{ #category : #accessing } +{ #category : 'accessing' } RabbitMQTextReverserTest >> queueName [ ^ 'tasks-for-' , testSelector ] -{ #category : #running } +{ #category : 'running' } RabbitMQTextReverserTest >> setUp [ super setUp. @@ -48,20 +49,20 @@ RabbitMQTextReverserTest >> setUp [ priority: Processor userBackgroundPriority ] -{ #category : #accessing } +{ #category : 'accessing' } RabbitMQTextReverserTest >> storeText: string [ reversedTexts add: string ] -{ #category : #running } +{ #category : 'running' } RabbitMQTextReverserTest >> tearDown [ workerProcess terminate. super tearDown ] -{ #category : #tests } +{ #category : 'tests' } RabbitMQTextReverserTest >> testProcessingMessages [ workerProcess resume. @@ -81,7 +82,7 @@ RabbitMQTextReverserTest >> testProcessingMessages [ assert: reversedTexts last equals: 'dlroW' ] -{ #category : #tests } +{ #category : 'tests' } RabbitMQTextReverserTest >> testProcessingOneMessage [ workerProcess resume. @@ -95,13 +96,13 @@ RabbitMQTextReverserTest >> testProcessingOneMessage [ self withTheOnlyOneIn: reversedTexts do: [ :text | self assert: text equals: 'olleH' ] ] -{ #category : #private } +{ #category : 'private' } RabbitMQTextReverserTest >> wait [ ( Delay forMilliseconds: 200 ) wait ] -{ #category : #private } +{ #category : 'private' } RabbitMQTextReverserTest >> withLocalhostConnectionDo: block [ | connection | diff --git a/source/Ansible-RabbitMQ-Tests/package.st b/source/Ansible-RabbitMQ-Tests/package.st index 5696bc23..9f5d4280 100644 --- a/source/Ansible-RabbitMQ-Tests/package.st +++ b/source/Ansible-RabbitMQ-Tests/package.st @@ -1 +1 @@ -Package { #name : #'Ansible-RabbitMQ-Tests' } +Package { #name : 'Ansible-RabbitMQ-Tests' } diff --git a/source/Ansible-RabbitMQ/Amqp091.extension.st b/source/Ansible-RabbitMQ/Amqp091.extension.st index 7bd2d386..f5ad1372 100644 --- a/source/Ansible-RabbitMQ/Amqp091.extension.st +++ b/source/Ansible-RabbitMQ/Amqp091.extension.st @@ -1,12 +1,12 @@ -Extension { #name : #Amqp091 } +Extension { #name : 'Amqp091' } -{ #category : #'*Ansible-RabbitMQ' } +{ #category : '*Ansible-RabbitMQ' } Amqp091 class >> basicNackMethod [ ^methodIds at: 3932280 ifAbsentPut: [Amqp091BasicNack] ] -{ #category : #'*Ansible-RabbitMQ' } +{ #category : '*Ansible-RabbitMQ' } Amqp091 class >> confirmSelectMethod [ ^methodIds diff --git a/source/Ansible-RabbitMQ/Amqp091BasicNack.class.st b/source/Ansible-RabbitMQ/Amqp091BasicNack.class.st index eb3950d1..caa7bc60 100644 --- a/source/Ansible-RabbitMQ/Amqp091BasicNack.class.st +++ b/source/Ansible-RabbitMQ/Amqp091BasicNack.class.st @@ -2,29 +2,30 @@ The [AMQP 0-9-1 specification](https://www.rabbitmq.com/specification.html) defines the basic.reject method that allows clients to reject individual, delivered messages, instructing the broker to either discard them or requeue them. Unfortunately, basic.reject provides no support for negatively acknowledging messages in bulk. To solve this, RabbitMQ supports the basic.nack method that provides all the functionality of basic.reject whilst also allowing for bulk processing of messages. " Class { - #name : #Amqp091BasicNack, - #superclass : #AmqpProtocolMethod, + #name : 'Amqp091BasicNack', + #superclass : 'AmqpProtocolMethod', #instVars : [ 'multiple', 'deliveryTag', 'requeue' ], - #category : #'Ansible-RabbitMQ' + #category : 'Ansible-RabbitMQ', + #package : 'Ansible-RabbitMQ' } -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> acceptableResponseClasses [ - ^ #() + ^ #() ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> codecMethodId [ ^ 3932280 ] -{ #category : #decoding } +{ #category : 'decoding' } Amqp091BasicNack >> decodeFrom: codec [ | bitBuffer | @@ -35,19 +36,19 @@ Amqp091BasicNack >> decodeFrom: codec [ requeue := ( bitBuffer bitAnd: 2 ) ~= 0 ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> deliveryTag [ ^ deliveryTag ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> deliveryTag: aValue [ deliveryTag := aValue ] -{ #category : #encoding } +{ #category : 'encoding' } Amqp091BasicNack >> encodeOn: codec [ | bitBuffer | @@ -59,31 +60,31 @@ Amqp091BasicNack >> encodeOn: codec [ codec nextOctetPut: bitBuffer ] -{ #category : #testing } +{ #category : 'testing' } Amqp091BasicNack >> hasContents [ ^ false ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> multiple [ ^ multiple ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> multiple: aValue [ multiple := aValue ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> requeue [ ^requeue ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091BasicNack >> requeue: aValue [ requeue := aValue diff --git a/source/Ansible-RabbitMQ/Amqp091ConfirmSelect.class.st b/source/Ansible-RabbitMQ/Amqp091ConfirmSelect.class.st index 4f368a62..5a504770 100644 --- a/source/Ansible-RabbitMQ/Amqp091ConfirmSelect.class.st +++ b/source/Ansible-RabbitMQ/Amqp091ConfirmSelect.class.st @@ -4,27 +4,28 @@ Using standard AMQP 0-9-1, the only way to guarantee that a message isn't lost i See [Publisher confirms](https://www.rabbitmq.com/confirms.html#publisher-confirms) " Class { - #name : #Amqp091ConfirmSelect, - #superclass : #AmqpProtocolMethod, + #name : 'Amqp091ConfirmSelect', + #superclass : 'AmqpProtocolMethod', #instVars : [ 'noWait' ], - #category : #'Ansible-RabbitMQ' + #category : 'Ansible-RabbitMQ', + #package : 'Ansible-RabbitMQ' } -{ #category : #accessing } +{ #category : 'accessing' } Amqp091ConfirmSelect >> acceptableResponseClasses [ ^ Array with: Amqp091ConfirmSelectOk ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091ConfirmSelect >> codecMethodId [ ^ 5570570 ] -{ #category : #decoding } +{ #category : 'decoding' } Amqp091ConfirmSelect >> decodeFrom: codec [ | bitBuffer | @@ -33,7 +34,7 @@ Amqp091ConfirmSelect >> decodeFrom: codec [ noWait := (bitBuffer bitAnd: 1) ~= 0 ] -{ #category : #encoding } +{ #category : 'encoding' } Amqp091ConfirmSelect >> encodeOn: codec [ | bitBuffer | @@ -43,19 +44,19 @@ Amqp091ConfirmSelect >> encodeOn: codec [ codec nextOctetPut: bitBuffer ] -{ #category : #testing } +{ #category : 'testing' } Amqp091ConfirmSelect >> hasContents [ ^false ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091ConfirmSelect >> noWait [ ^ noWait ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091ConfirmSelect >> noWait: aValue [ noWait := aValue diff --git a/source/Ansible-RabbitMQ/Amqp091ConfirmSelectOk.class.st b/source/Ansible-RabbitMQ/Amqp091ConfirmSelectOk.class.st index 298d39e4..97995988 100644 --- a/source/Ansible-RabbitMQ/Amqp091ConfirmSelectOk.class.st +++ b/source/Ansible-RabbitMQ/Amqp091ConfirmSelectOk.class.st @@ -1,30 +1,31 @@ Class { - #name : #Amqp091ConfirmSelectOk, - #superclass : #AmqpProtocolMethod, - #category : #'Ansible-RabbitMQ' + #name : 'Amqp091ConfirmSelectOk', + #superclass : 'AmqpProtocolMethod', + #category : 'Ansible-RabbitMQ', + #package : 'Ansible-RabbitMQ' } -{ #category : #accessing } +{ #category : 'accessing' } Amqp091ConfirmSelectOk >> acceptableResponseClasses [ ^ #() ] -{ #category : #accessing } +{ #category : 'accessing' } Amqp091ConfirmSelectOk >> codecMethodId [ ^ 5570571 ] -{ #category : #decoding } +{ #category : 'decoding' } Amqp091ConfirmSelectOk >> decodeFrom: codec [ ] -{ #category : #encoding } +{ #category : 'encoding' } Amqp091ConfirmSelectOk >> encodeOn: codec [ ] -{ #category : #testing } +{ #category : 'testing' } Amqp091ConfirmSelectOk >> hasContents [ ^false diff --git a/source/Ansible-RabbitMQ/AmqpChannel.extension.st b/source/Ansible-RabbitMQ/AmqpChannel.extension.st index ed87b3b7..0b39a552 100644 --- a/source/Ansible-RabbitMQ/AmqpChannel.extension.st +++ b/source/Ansible-RabbitMQ/AmqpChannel.extension.st @@ -1,6 +1,6 @@ -Extension { #name : #AmqpChannel } +Extension { #name : 'AmqpChannel' } -{ #category : #'*Ansible-RabbitMQ' } +{ #category : '*Ansible-RabbitMQ' } AmqpChannel >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ self rpc: protocolVersion confirmSelectMethod new. diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index cd8bdcdb..0e223a14 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -5,34 +5,35 @@ I know how to connect to Rabbit MQ. My subclasses will act as different roles depending on the system needs. " Class { - #name : #RabbitMQClient, - #superclass : #Object, + #name : 'RabbitMQClient', + #superclass : 'Object', #instVars : [ 'connection' ], - #category : #'Ansible-RabbitMQ' + #category : 'Ansible-RabbitMQ', + #package : 'Ansible-RabbitMQ' } -{ #category : #testing } +{ #category : 'testing' } RabbitMQClient class >> isAbstract [ ^ self = RabbitMQClient ] -{ #category : #'private - configuring' } +{ #category : 'private - configuring' } RabbitMQClient >> configureConnection: builder [ self subclassResponsibility ] -{ #category : #'private - configuring' } +{ #category : 'private - configuring' } RabbitMQClient >> connectivityErrors [ ^ NetworkError ] -{ #category : #initialization } +{ #category : 'initialization' } RabbitMQClient >> initializeConnection [ | builder | @@ -42,7 +43,7 @@ RabbitMQClient >> initializeConnection [ connection := builder build ] -{ #category : #'private - connecting' } +{ #category : 'private - connecting' } RabbitMQClient >> logFailedConnectionAttempt: attemptNumber dueTo: error [ LaunchpadLogRecord emitError: ( 'Attempt #<1p>/<2p> to connect to RabbitMQ failed: <3s>' @@ -51,7 +52,7 @@ RabbitMQClient >> logFailedConnectionAttempt: attemptNumber dueTo: error [ with: error messageText ) ] -{ #category : #'private - connecting' } +{ #category : 'private - connecting' } RabbitMQClient >> openConnection [ self withSuccessfulConnectionDo: [ :succesfulConnection | @@ -64,19 +65,19 @@ RabbitMQClient >> openConnection [ ] ] -{ #category : #'private - configuring' } +{ #category : 'private - configuring' } RabbitMQClient >> options [ ^ Dictionary new ] -{ #category : #'private - configuring' } +{ #category : 'private - configuring' } RabbitMQClient >> retryCount [ ^ self options at: #retryCount ifAbsent: [ 2 ] ] -{ #category : #controlling } +{ #category : 'controlling' } RabbitMQClient >> start [ self openConnection. @@ -92,13 +93,13 @@ RabbitMQClient >> start [ ] ] -{ #category : #private } +{ #category : 'private' } RabbitMQClient >> startProcessing [ self subclassResponsibility ] -{ #category : #controlling } +{ #category : 'controlling' } RabbitMQClient >> stop [ connection @@ -106,7 +107,7 @@ RabbitMQClient >> stop [ whenNot: [ LaunchpadLogRecord emitWarning: 'RabbitMQ connection was already closed.' ] ] -{ #category : #'private - connecting' } +{ #category : 'private - connecting' } RabbitMQClient >> try: aBlock onConnectivityErrorDo: failBlock [ Retry value: aBlock configuredBy: [ :retry | @@ -117,7 +118,7 @@ RabbitMQClient >> try: aBlock onConnectivityErrorDo: failBlock [ ] ] -{ #category : #'private - connecting' } +{ #category : 'private - connecting' } RabbitMQClient >> withSuccessfulConnectionDo: aBlock [ self diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 075a94aa..bbecd941 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -12,19 +12,20 @@ This concept is especially useful in web applications where it's impossible to h task during a short HTTP request window. " Class { - #name : #RabbitMQWorker, - #superclass : #RabbitMQClient, - #category : #'Ansible-RabbitMQ' + #name : 'RabbitMQWorker', + #superclass : 'RabbitMQClient', + #category : 'Ansible-RabbitMQ', + #package : 'Ansible-RabbitMQ' } -{ #category : #testing } +{ #category : 'testing' } RabbitMQWorker class >> isAbstract [ ^ self = RabbitMQWorker ] -{ #category : #private } +{ #category : 'private' } RabbitMQWorker >> process: payload [ "A message was received including the bytes in `payload`" @@ -32,13 +33,13 @@ RabbitMQWorker >> process: payload [ self subclassResponsibility ] -{ #category : #accessing } +{ #category : 'accessing' } RabbitMQWorker >> queueName [ ^ self subclassResponsibility ] -{ #category : #private } +{ #category : 'private' } RabbitMQWorker >> startProcessing [ | channel | diff --git a/source/Ansible-RabbitMQ/package.st b/source/Ansible-RabbitMQ/package.st index b41fc2eb..ee52d3d0 100644 --- a/source/Ansible-RabbitMQ/package.st +++ b/source/Ansible-RabbitMQ/package.st @@ -1 +1 @@ -Package { #name : #'Ansible-RabbitMQ' } +Package { #name : 'Ansible-RabbitMQ' } diff --git a/source/BaselineOfAnsible/BaselineOfAnsible.class.st b/source/BaselineOfAnsible/BaselineOfAnsible.class.st index 2f750d2d..2a0facf3 100644 --- a/source/BaselineOfAnsible/BaselineOfAnsible.class.st +++ b/source/BaselineOfAnsible/BaselineOfAnsible.class.st @@ -1,10 +1,11 @@ Class { - #name : #BaselineOfAnsible, - #superclass : #BaselineOf, - #category : #BaselineOfAnsible + #name : 'BaselineOfAnsible', + #superclass : 'BaselineOf', + #category : 'BaselineOfAnsible', + #package : 'BaselineOfAnsible' } -{ #category : #baselines } +{ #category : 'baselines' } BaselineOfAnsible >> baseline: spec [ @@ -21,13 +22,13 @@ BaselineOfAnsible >> baseline: spec [ ] ] -{ #category : #accessing } +{ #category : 'accessing' } BaselineOfAnsible >> projectClass [ - ^ MetacelloCypressBaselineProject + ^ MetacelloCypressBaselineProject ] -{ #category : #initialization } +{ #category : 'initialization' } BaselineOfAnsible >> setUpDependencies: spec [ spec @@ -48,7 +49,7 @@ BaselineOfAnsible >> setUpDependencies: spec [ project: 'Hyperspace-Deployment' copyFrom: 'Hyperspace' with: [ spec loads: 'Deployment' ] ] -{ #category : #baselines } +{ #category : 'baselines' } BaselineOfAnsible >> setUpDeploymentPackages: spec [ spec @@ -69,7 +70,7 @@ BaselineOfAnsible >> setUpDeploymentPackages: spec [ group: 'RabbitMQ' with: 'Ansible-RabbitMQ' ] -{ #category : #baselines } +{ #category : 'baselines' } BaselineOfAnsible >> setUpTestPackages: spec [ spec @@ -83,7 +84,7 @@ BaselineOfAnsible >> setUpTestPackages: spec [ group: 'Tests' with: 'Ansible-RabbitMQ-Tests' ] -{ #category : #baselines } +{ #category : 'baselines' } BaselineOfAnsible >> setUpToolsPackages: spec [ spec diff --git a/source/BaselineOfAnsible/package.st b/source/BaselineOfAnsible/package.st index 8b38b143..bede34c8 100644 --- a/source/BaselineOfAnsible/package.st +++ b/source/BaselineOfAnsible/package.st @@ -1 +1 @@ -Package { #name : #BaselineOfAnsible } +Package { #name : 'BaselineOfAnsible' } From 6e224b7b854026f120462ac2eb00e1b36e480e37 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Wed, 10 Jul 2024 12:10:42 -0300 Subject: [PATCH 02/65] :sparkles: Add RabbitMQPublisher --- .../RabbitMQPublisher.class.st | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 source/Ansible-RabbitMQ/RabbitMQPublisher.class.st diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st new file mode 100644 index 00000000..f11dfdfc --- /dev/null +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -0,0 +1,161 @@ +Class { + #name : 'RabbitMQPublisher', + #superclass : 'Object', + #instVars : [ + 'options', + 'builder', + 'channel', + 'connection' + ], + #category : 'Ansible-RabbitMQ', + #package : 'Ansible-RabbitMQ' +} + +{ #category : 'instance creation' } +RabbitMQPublisher class >> configuredBy: aConfigurationAction [ + + | options | + + options := Dictionary new. + aConfigurationAction value: options. + ^ self new initializeConfiguredBy: options +] + +{ #category : 'accessing' } +RabbitMQPublisher >> channel [ + + ^channel +] + +{ #category : 'private - accessing' } +RabbitMQPublisher >> connectivityErrors [ + + ^ AmqpDisconnectedError , NetworkError +] + +{ #category : 'private - connecting' } +RabbitMQPublisher >> ensureChannelOpen [ + + | reconnect | + + reconnect := [ + self ensureConnectedAndOpen. + channel := connection createChannel]. + channel ifNil: reconnect ifNotNil: [channel whenOpenDo: [] whenClosedDo: reconnect] +] + +{ #category : 'private - connecting' } +RabbitMQPublisher >> ensureConnectedAndOpen [ + + | createConnection | + + createConnection := [ + self + try: [ + connection := builder build. + connection open] + onConnectivityErrorDo: [:attemptNumber :error | + self logFailedConnectionAttempt: attemptNumber dueTo: error]]. + + connection + ifNil: createConnection + ifNotNil: [connection whenConnected: [] whenNot: createConnection]. + + connection + whenOpen: [] + whenNot: [connection closeReason ifNil: [connection open] ifNotNil: createConnection] +] + +{ #category : 'initialization' } +RabbitMQPublisher >> initializeConfiguredBy: anOptionsDictionary [ + + options := anOptionsDictionary. + builder := AmqpConnectionBuilder usingAMQP091Protocol. + builder hostname: (options at: #hostname). + builder portNumber: (options at: #port ifAbsent: [5672]). + builder username: (options at: #username ifAbsent: ['guest']). + builder password: (options at: #password ifAbsent: ['guest']) +] + +{ #category : 'private - logging' } +RabbitMQPublisher >> logFailedConnectionAttempt: anAttemptNumber dueTo: anError [ + + LogRecord emitError: ( 'Attempt #<1p>/<2p> to connect to RabbitMQ failed: <3s>' + expandMacrosWith: anAttemptNumber + with: self retryCount + with: anError messageText ) +] + +{ #category : 'publishing' } +RabbitMQPublisher >> publish: aMessageCollection onQueueNamed: aQueueName [ + + aMessageCollection do: [:message | self publishOnly: message onQueueNamed: aQueueName] +] + +{ #category : 'publishing' } +RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ + + self ensureChannelOpen. + + self shouldLogDebuggingInfo then: [ + LogRecord + emitStructuredDebuggingInfo: 'RabbitMQ message published' + with: [:data | + data + at: #messagePublished put: aMessage; + at: #routingKey put: aQueueName; + at: #connectionDescription put: connection connectionPairsDescription]]. + + channel + basicPublish: aMessage utf8Encoded + exchange: '' + routingKey: aQueueName + properties: (connection protocolClass basicPropertiesClass new deliveryMode: 2) +] + +{ #category : 'private - accessing' } +RabbitMQPublisher >> retryCount [ + + ^options at: #maximumConnectionAttemps ifAbsent: [3] +] + +{ #category : 'private - testing' } +RabbitMQPublisher >> shouldLogDebuggingInfo [ + + ^options at: #enableDebuggingLogs ifAbsent: [false] +] + +{ #category : 'connecting' } +RabbitMQPublisher >> start [ + + self ensureChannelOpen +] + +{ #category : 'connecting' } +RabbitMQPublisher >> stop [ + + connection + ifNil: [] + ifNotNil: [ + connection + whenConnected: [connection close] + whenNot: [LogRecord emitWarning: 'RabbitMQ connection was already closed.']] +] + +{ #category : 'private - accessing' } +RabbitMQPublisher >> timeframeBetweenAttempts [ + + ^Duration milliSeconds: (options at: #timeSlotBetweenConnectionRetriesInMs ifAbsent: [300]) +] + +{ #category : 'private - connecting' } +RabbitMQPublisher >> try: aBlock onConnectivityErrorDo: failBlock [ + + Retry + value: aBlock + configuredBy: [:retry | + retry + upTo: self retryCount; + backoffExponentiallyWithTimeSlot: self timeframeBetweenAttempts; + on: self connectivityErrors evaluating: failBlock] +] From e15581e752417e34e692fd0fd0a28950b832555a Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Wed, 10 Jul 2024 12:11:17 -0300 Subject: [PATCH 03/65] :white_check_mark: Add RabbitMQPublisher tests --- .../AMQPRabbitTest.class.st | 2 +- .../RabbitMQPublisherTest.class.st | 238 ++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st diff --git a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st b/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st index af743de5..21cd8d80 100644 --- a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st @@ -6,7 +6,7 @@ Class { } { #category : 'private' } -AMQPRabbitTest >> publish: aMessageCollection onQueueNamed: aQueueName [ +AMQPRabbitTest >> publish: aMessageCollection onQueueNamed: aQueueName [ self withLocalhostConnectionDo: [ :connection | | channel queue confirmationWasReceived | diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st new file mode 100644 index 00000000..9b36a83b --- /dev/null +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st @@ -0,0 +1,238 @@ +Class { + #name : 'RabbitMQPublisherTest', + #superclass : 'TestCase', + #instVars : [ + 'workerProcess', + 'publisher', + 'reversedTexts' + ], + #category : 'Ansible-RabbitMQ-Tests', + #package : 'Ansible-RabbitMQ-Tests' +} + +{ #category : 'accessing' } +RabbitMQPublisherTest class >> defaultTimeLimit [ + + ^ ( Socket standardTimeout + 60 ) seconds +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> configureConnection: aBuilder [ + + aBuilder hostname: 'localhost'. + aBuilder portNumber: 5672. + aBuilder username: 'guest'. + aBuilder password: 'guest' +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> defaultRabbitMQPublisher [ + + ^ RabbitMQPublisher configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #port put: 5672; + at: #username: put: 'guest'; + at: #password put: 'guest' + ] +] + +{ #category : 'running' } +RabbitMQPublisherTest >> initialize [ + + super initialize. + reversedTexts := OrderedCollection new +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> publish: anObject onQueueNamed: aQueueName [ + + publisher publish: anObject onQueueNamed: aQueueName +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> queueName [ + + ^ 'tasks-for-' , testSelector +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> rabbitMQPublisherWithDebuggingLogs [ + + ^ RabbitMQPublisher configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #port put: 5672; + at: #username: put: 'guest'; + at: #password put: 'guest'; + at: #enableDebuggingLogs put: true + ] +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> resumeWorkerDuring: aBlock [ + + workerProcess resume. + Processor yield. + self wait. + aBlock value. + self wait +] + +{ #category : 'running' } +RabbitMQPublisherTest >> setUp [ + + super setUp. + + workerProcess := + [| worker | + worker := RabbitMQTextReverser workingWith: self. + [worker start] ensure: [worker stop]] + newProcess. + + workerProcess + name: 'Text reverser worker'; + priority: Processor userBackgroundPriority. + + publisher := self defaultRabbitMQPublisher. + publisher start. + publisher channel queueDeclare: self queueName durable: true +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> storeText: aString [ + + reversedTexts add: aString +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> tearDown [ + + publisher channel queueDelete: self queueName. + publisher stop. + workerProcess terminate. + super tearDown +] + +{ #category : 'running' } +RabbitMQPublisherTest >> testCannotStartBecauseNotFoundARabbitMQService [ + + | anotherPublisher logger expectedErrorDescription | + + logger := MemoryLogger new. + + anotherPublisher := + RabbitMQPublisher configuredBy: [:options | + options + at: #hostname put: 'looney-tunes.com'; + at: #port put: 1234; + at: #username put: 'bugs'; + at: #password put: 'bunny'; + at: #maximumConnectionAttemps put: 1; + at: #timeSlotBetweenConnectionRetriesInMs put: 1]. + + expectedErrorDescription := 'connection closed while sending data'. + + logger runDuring: [ + self + should: [anotherPublisher start] + raise: ConnectionClosed + description: expectedErrorDescription]. + + self + assert: logger recordings size equals: 3; + assert: ( + logger recordings second printString endsWith: + (#'Attempt #1/1 to connect to RabbitMQ failed: <1s>' expandMacrosWith: expectedErrorDescription)) +] + +{ #category : 'running' } +RabbitMQPublisherTest >> testDebuggingLogsEnabled [ + + | anotherPublisher logger | + + logger := MemoryLogger new. + anotherPublisher := self rabbitMQPublisherWithDebuggingLogs. + + logger runDuring: [ + anotherPublisher start. + anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. + anotherPublisher stop + ]. + + self assert: logger recordings size equals: 3. + + self assert: ( '*[INFO] AMQP connection *->localhost:5672 established successfully' match: + ( logger recordings at: 1 ) printString ). + + self assert: + ( '* [DEBUG] RabbitMQ message published {"messagePublished":"Hola!","routingKey":"tasks-for-testDebuggingLogsEnabled","connectionDescription":"*->localhost:5672"}' + match: ( logger recordings at: 2 ) printString ). + + self assert: ( '* [INFO] AMQP connection *->localhost:5672 closed normally' match: + ( logger recordings at: 3 ) printString ) +] + +{ #category : 'running' } +RabbitMQPublisherTest >> testDebuggingLogsTurnedOff [ + + | anotherPublisher logger | + + logger := MemoryLogger new. + anotherPublisher := self defaultRabbitMQPublisher. + + logger runDuring: [ + anotherPublisher start. + anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. + anotherPublisher stop + ]. + + self assert: logger recordings size equals: 2. + + self assert: ( '*[INFO] AMQP connection *->localhost:5672 established successfully' match: + ( logger recordings at: 1 ) printString ). + + self assert: ( '* [INFO] AMQP connection *->localhost:5672 closed normally' match: + ( logger recordings at: 2 ) printString ) +] + +{ #category : 'running' } +RabbitMQPublisherTest >> testPublishingMessageWhenChannelIsTemporallyLost [ + + self resumeWorkerDuring: [ + publisher channel close. + self publish: #( 'test channel restored' ) onQueueNamed: self queueName + ]. + + self + withTheOnlyOneIn: reversedTexts + do: [ :reversedText | self assert: reversedText equals: 'derotser lennahc tset' ] +] + +{ #category : 'running' } +RabbitMQPublisherTest >> testPublishingMessages [ + + self resumeWorkerDuring: [ + self publish: #( 'first message' 'second message' ) onQueueNamed: self queueName ]. + + self + assert: reversedTexts size equals: 2; + assert: reversedTexts first equals: 'egassem tsrif'; + assert: reversedTexts second equals: 'egassem dnoces' +] + +{ #category : 'running' } +RabbitMQPublisherTest >> testPublishingOneMessage [ + + self resumeWorkerDuring: [ publisher publishOnly: 'one message' onQueueNamed: self queueName ]. + + self + withTheOnlyOneIn: reversedTexts + do: [ :reversedText | self assert: reversedText equals: 'egassem eno' ] +] + +{ #category : 'private - accessing' } +RabbitMQPublisherTest >> wait [ + + ( Delay forMilliseconds: 200 ) wait +] From e91cddac8691403143303eed715e8d7959b6fec6 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Wed, 10 Jul 2024 12:14:14 -0300 Subject: [PATCH 04/65] :sparkles: Add support for reconnection logic and also enhance logging --- .../AmqpChannel.class.st | 8 ++ .../AmqpChannelHandler.class.st | 6 + .../AmqpConnection.class.st | 111 ++++++++++++++---- .../HealthyClosedSocketConnection.class.st | 27 +++++ 4 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 source/Ansible-Protocol-Core/HealthyClosedSocketConnection.class.st diff --git a/source/Ansible-Protocol-Core/AmqpChannel.class.st b/source/Ansible-Protocol-Core/AmqpChannel.class.st index 6cb6cf49..8120d891 100644 --- a/source/Ansible-Protocol-Core/AmqpChannel.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannel.class.st @@ -489,3 +489,11 @@ AmqpChannel >> update: aSymbol [ ifTrue: [ self changed: #channelClosed ]. ^ super update: aSymbol ] + +{ #category : 'accessing' } +AmqpChannel >> whenOpenDo: aBlock whenClosedDo: aClosedHandler [ + + ^handler whenOpenDo: aBlock whenClosedDo: aClosedHandler + + +] diff --git a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st index da05eb17..a9945890 100644 --- a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st @@ -179,3 +179,9 @@ AmqpChannelHandler >> waitForReply: acceptableReplies [ ] ] repeat ] + +{ #category : 'accessing' } +AmqpChannelHandler >> whenOpenDo: aBlock whenClosedDo: aClosedHandler [ + + ^closeReason ifNil: aBlock ifNotNil: aClosedHandler +] diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 5388ebbc..beb8bea4 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -37,19 +37,30 @@ AmqpConnection >> close [ | connectionClose | - heartbeatSender stop. - isOpen ifTrue: [ + isOpen ifTrue: [ isOpen := false. connectionClose := self protocolClass connectionCloseMethod new replyCode: self protocolClass replySuccess; replyText: 'Normal shutdown'; classId: 0; methodId: 0. - self rpc: connectionClose onChannel: 0. + self + rpc: connectionClose + onChannel: 0 + ifConnectionClosesWhileWaitingReplyDo: [ :signal | "https://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.close-ok A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error. - jvanecek" + LogRecord emitWarning: + ( 'AMQP connection <1s> closed while waiting for the close-ok reply' expandMacrosWith: + self connectionPairsDescription ). + signal return + ]. self internalClose: connectionClose ]. - socket close + LogRecord emitInfo: + ( 'AMQP connection <1s> closed normally' expandMacrosWith: self connectionPairsDescription ). + heartbeatSender stop. + socket close. + socketConnectionStatus := HealthyClosedSocketConnection dueTo: 'Normal shutdown' ] { #category : 'connection-handling' } @@ -64,6 +75,16 @@ AmqpConnection >> codec [ ^ codec ] +{ #category : 'accessing' } +AmqpConnection >> connectionPairsDescription [ + + ^ '<1s>:<2s>-><3s>:<4s>' + expandMacrosWith: socket peerName asString + with: socket port asString + with: hostname + with: portNumber asString +] + { #category : 'connection-handling' } AmqpConnection >> createChannel [ @@ -104,19 +125,26 @@ AmqpConnection >> handleConnectionClose: cmd [ ] { #category : 'connection-handling' } -AmqpConnection >> hardClose [ +AmqpConnection >> hardCloseDescribedWith: aDescription [ | connectionClose | isOpen - ifTrue: [ isOpen := false. - connectionClose := self protocolClass connectionCloseMethod new - replyCode: self protocolClass internalError; - replyText: 'Abnormal disconnection'. - self internalClose: connectionClose - ]. - heartbeatSender stop. - socket close + ifTrue: [ + isOpen := false. + connectionClose := + (self protocolClass connectionCloseMethod new) + replyCode: self protocolClass internalError; + replyText: 'Abnormal disconnection'. + self internalClose: connectionClose]. + + LogRecord emitWarning: ( + 'AMQP connection <1s> hard closed due to <2s>' + expandMacrosWith: self connectionPairsDescription with: aDescription). + + heartbeatSender stop. + socket close. + socketConnectionStatus := FailedSocketConnection dueTo: aDescription ] { #category : 'initialization' } @@ -140,14 +168,27 @@ AmqpConnection >> initializeHeartbeatSender [ AmqpConnection >> initializeSocketConnection [ socket := Socket newTCP. - [ + [ socket connectToHostNamed: hostname port: portNumber. socketConnectionStatus := SuccesfulSocketConnection new ] on: NetworkError - do: [ :error | + do: [ :error | socketConnectionStatus := FailedSocketConnection dueTo: error. error return + ]. + + self + whenConnected: [ + LogRecord emitInfo: + ( 'AMQP connection <1s> established successfully' expandMacrosWith: + self connectionPairsDescription ) + ] + whenNot: [ :error | + LogRecord emitInfo: ( 'AMQP connection to <1s>:<2s> failed to establish because <3s>' + expandMacrosWith: hostname + with: portNumber printString + with: error printString ) ] ] @@ -188,11 +229,12 @@ AmqpConnection >> mainLoopCycle [ | frame | frame := self nextFrame. - frame - ifNil: [ self hardClose. - ^ self ]. - (frame isKindOf: AmqpHeartbeatFrame) - ifFalse: [ (channels at: frame channelNumber) handleFrame: frame ] + frame ifNil: [ + self hardCloseDescribedWith: 'Failed retrieving next frame'. + ^ self + ]. + ( frame isKindOf: AmqpHeartbeatFrame ) ifFalse: [ + ( channels at: frame channelNumber ) handleFrame: frame ] ] { #category : 'connection-handling' } @@ -270,6 +312,12 @@ AmqpConnection >> openConnection [ isOpen := true. ] +{ #category : 'connection-handling' } +AmqpConnection >> printOn: aStream [ + + aStream nextPutAll: ( 'AMPQ Connection on <1s>' expandMacrosWith: self connectionPairsDescription ) +] + { #category : 'connection-handling' } AmqpConnection >> processAsyncEvents [ @@ -291,13 +339,22 @@ AmqpConnection >> resetChannel: channelNumber [ ] { #category : 'connection-handling' } -AmqpConnection >> rpc: requestMethod onChannel: channelNumber [ +AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber [ + + ^ self + rpc: aRequestMethod + onChannel: aChannelNumber + ifConnectionClosesWhileWaitingReplyDo: [ :signal | signal pass ] +] + +{ #category : 'initialization' } +AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber ifConnectionClosesWhileWaitingReplyDo: aBlock [ | ch | - ch := self ensureChannel: channelNumber. - self sendMethod: requestMethod onChannel: channelNumber. - ^ ch waitForReply: requestMethod acceptableResponseClasses + ch := self ensureChannel: aChannelNumber. + self sendMethod: aRequestMethod onChannel: aChannelNumber. + ^ ch waitForReply: aRequestMethod acceptableResponseClasses ] { #category : 'connection-handling' } @@ -431,3 +488,9 @@ AmqpConnection >> whenConnected: aBlock whenNot: anotherBlock [ ^socketConnectionStatus whenConnected: aBlock whenNot: anotherBlock ] + +{ #category : 'connection-handling' } +AmqpConnection >> whenOpen: aBlock whenNot: aClosedHandler [ + + ^isOpen then: aBlock otherwise: aClosedHandler +] diff --git a/source/Ansible-Protocol-Core/HealthyClosedSocketConnection.class.st b/source/Ansible-Protocol-Core/HealthyClosedSocketConnection.class.st new file mode 100644 index 00000000..db8db660 --- /dev/null +++ b/source/Ansible-Protocol-Core/HealthyClosedSocketConnection.class.st @@ -0,0 +1,27 @@ +Class { + #name : 'HealthyClosedSocketConnection', + #superclass : 'SocketConnectionStatus', + #instVars : [ + 'error' + ], + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' +} + +{ #category : 'instance creation' } +HealthyClosedSocketConnection class >> dueTo: anError [ + + ^ self new initializeDueTo: anError +] + +{ #category : 'initialization' } +HealthyClosedSocketConnection >> initializeDueTo: anError [ + + error := anError +] + +{ #category : 'initialization' } +HealthyClosedSocketConnection >> whenConnected: aBlock whenNot: anotherBlock [ + + ^ anotherBlock cull: error +] From 7c49c014afde450c06e95cc2648f16c73784f299 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Wed, 10 Jul 2024 12:14:56 -0300 Subject: [PATCH 05/65] :sparkles: Add support for client reconnection --- .../RabbitMQTextReverserTest.class.st | 13 ++++---- .../Ansible-RabbitMQ/RabbitMQClient.class.st | 5 ++-- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 30 +++++++++++++++++-- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st index 01897280..d786d55b 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st @@ -109,13 +109,12 @@ RabbitMQTextReverserTest >> withLocalhostConnectionDo: block [ connection := AmqpConnectionBuilder usingAMQP091Protocol build. + connection open. + connection - whenConnected: [ - connection open. - [ block value: connection ] ensure: [ connection close ] - ] - whenNot: [ :error | - connection hardClose. - self fail: error messageText + whenConnected: [ + block value: connection. + connection close ] + whenNot: [ :error | self fail: error messageText ] ] diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 0e223a14..3275d984 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -8,7 +8,8 @@ Class { #name : 'RabbitMQClient', #superclass : 'Object', #instVars : [ - 'connection' + 'connection', + 'builder' ], #category : 'Ansible-RabbitMQ', #package : 'Ansible-RabbitMQ' @@ -36,8 +37,6 @@ RabbitMQClient >> connectivityErrors [ { #category : 'initialization' } RabbitMQClient >> initializeConnection [ - | builder | - builder := AmqpConnectionBuilder usingAMQP091Protocol. self configureConnection: builder. connection := builder build diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index bbecd941..10401341 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -25,6 +25,28 @@ RabbitMQWorker class >> isAbstract [ ^ self = RabbitMQWorker ] +{ #category : 'private' } +RabbitMQWorker >> ensureConnectedAndOpen [ + + | createConnection | + + createConnection := [ + self + try: [ + connection := builder build. + connection open] + onConnectivityErrorDo: [:attemptNumber :error | + self logFailedConnectionAttempt: attemptNumber dueTo: error]]. + + connection + ifNil: createConnection + ifNotNil: [connection whenConnected: [] whenNot: createConnection]. + + connection + whenOpen: [] + whenNot: [connection closeReason ifNil: [connection open] ifNotNil: createConnection] +] + { #category : 'private' } RabbitMQWorker >> process: payload [ @@ -42,9 +64,13 @@ RabbitMQWorker >> queueName [ { #category : 'private' } RabbitMQWorker >> startProcessing [ - | channel | + | channel reconnect | - channel := connection createChannel. + reconnect := [ + self ensureConnectedAndOpen. + channel := connection createChannel]. + channel ifNil: reconnect ifNotNil: [channel whenOpenDo: [] whenClosedDo: reconnect]. + channel queueDeclare: self queueName durable: true. channel prefetchCount: 1. channel consumeFrom: self queueName applying: [ :message | From d69234f705098cbacd33ee9204b6bd1a7dbd7ea9 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Wed, 10 Jul 2024 15:03:42 -0300 Subject: [PATCH 06/65] :recycle: Refactor RabbitMQ code --- .../RabbitMQPublisherTest.class.st | 9 ++------ .../Ansible-RabbitMQ/RabbitMQClient.class.st | 22 +++++++++++++++++++ .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 22 ------------------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st index 9b36a83b..c3aa0127 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st @@ -37,13 +37,6 @@ RabbitMQPublisherTest >> defaultRabbitMQPublisher [ ] ] -{ #category : 'running' } -RabbitMQPublisherTest >> initialize [ - - super initialize. - reversedTexts := OrderedCollection new -] - { #category : 'private - accessing' } RabbitMQPublisherTest >> publish: anObject onQueueNamed: aQueueName [ @@ -83,6 +76,8 @@ RabbitMQPublisherTest >> resumeWorkerDuring: aBlock [ RabbitMQPublisherTest >> setUp [ super setUp. + + reversedTexts := OrderedCollection new. workerProcess := [| worker | diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 3275d984..f1222dfe 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -34,6 +34,28 @@ RabbitMQClient >> connectivityErrors [ ^ NetworkError ] +{ #category : 'private' } +RabbitMQClient >> ensureConnectedAndOpen [ + + | createConnection | + + createConnection := [ + self + try: [ + connection := builder build. + connection open] + onConnectivityErrorDo: [:attemptNumber :error | + self logFailedConnectionAttempt: attemptNumber dueTo: error]]. + + connection + ifNil: createConnection + ifNotNil: [connection whenConnected: [] whenNot: createConnection]. + + connection + whenOpen: [] + whenNot: [connection closeReason ifNil: [connection open] ifNotNil: createConnection] +] + { #category : 'initialization' } RabbitMQClient >> initializeConnection [ diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 10401341..1e6991c7 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -25,28 +25,6 @@ RabbitMQWorker class >> isAbstract [ ^ self = RabbitMQWorker ] -{ #category : 'private' } -RabbitMQWorker >> ensureConnectedAndOpen [ - - | createConnection | - - createConnection := [ - self - try: [ - connection := builder build. - connection open] - onConnectivityErrorDo: [:attemptNumber :error | - self logFailedConnectionAttempt: attemptNumber dueTo: error]]. - - connection - ifNil: createConnection - ifNotNil: [connection whenConnected: [] whenNot: createConnection]. - - connection - whenOpen: [] - whenNot: [connection closeReason ifNil: [connection open] ifNotNil: createConnection] -] - { #category : 'private' } RabbitMQWorker >> process: payload [ From 9ca32f0c562708be0e33acd5df860afaef4ab064 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Wed, 10 Jul 2024 15:04:15 -0300 Subject: [PATCH 07/65] :bulb: Added RabbitMQPublisher class comment --- source/Ansible-RabbitMQ/RabbitMQPublisher.class.st | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index f11dfdfc..efdaa27a 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -1,3 +1,11 @@ +" +I'm a publisher connected to a RabbitMQ queue. +I will send messages to the specified queue for further processing. + +The main idea behind publishing messages to a queue is to decouple the task of generating +messages from the task of processing them. This allows for more flexibility and scalability +as the producers and consumers can operate independently. +" Class { #name : 'RabbitMQPublisher', #superclass : 'Object', From 747cfdba6fb9dcb6dd4c0528558c5ff7300618be Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Thu, 11 Jul 2024 14:01:00 -0300 Subject: [PATCH 08/65] :recycle: Refactor RabbitMQWorker and its tests --- .../RabbitMQTextReverserTest.class.st | 59 +++++++++---------- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 56 ++++++++++++++---- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st index d786d55b..8362ff34 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st @@ -6,22 +6,23 @@ Class { #superclass : 'TestCase', #instVars : [ 'reversedTexts', - 'workerProcess' + 'workerProcess', + 'worker' ], #category : 'Ansible-RabbitMQ-Tests', #package : 'Ansible-RabbitMQ-Tests' } { #category : 'private' } -RabbitMQTextReverserTest >> publish: text onQueueNamed: aQueueName [ +RabbitMQTextReverserTest >> publish: aText onQueueNamed: aQueueName [ - self withLocalhostConnectionDo: [ :connection | + self withLocalhostConnectionDo: [ :connection | | channel | channel := connection createChannel. channel queueDeclare: aQueueName durable: true. channel - basicPublish: text utf8Encoded + basicPublish: aText utf8Encoded exchange: '' routingKey: aQueueName properties: ( connection protocolClass basicPropertiesClass new deliveryMode: 2 ) @@ -34,25 +35,34 @@ RabbitMQTextReverserTest >> queueName [ ^ 'tasks-for-' , testSelector ] +{ #category : 'private' } +RabbitMQTextReverserTest >> resumeWorkerDuring: aBlock [ + + workerProcess resume. + Processor yield. + self wait. + aBlock value. + self wait +] + { #category : 'running' } RabbitMQTextReverserTest >> setUp [ super setUp. reversedTexts := OrderedCollection new. - workerProcess := [ - | worker | - worker := RabbitMQTextReverser workingWith: self. - [ worker start ] ensure: [ worker stop ] - ] newProcess. + + worker := RabbitMQTextReverser workingWith: self. + + workerProcess := [ [ worker start ] ensure: [ worker stop ] ] newProcess. workerProcess name: 'Text reverser worker'; priority: Processor userBackgroundPriority ] { #category : 'accessing' } -RabbitMQTextReverserTest >> storeText: string [ +RabbitMQTextReverserTest >> storeText: aString [ - reversedTexts add: string + reversedTexts add: aString ] { #category : 'running' } @@ -65,16 +75,11 @@ RabbitMQTextReverserTest >> tearDown [ { #category : 'tests' } RabbitMQTextReverserTest >> testProcessingMessages [ - workerProcess resume. - - Processor yield. - self wait. - - self - publish: 'Hello' onQueueNamed: self queueName; - publish: 'World' onQueueNamed: self queueName. - - self wait. + self resumeWorkerDuring: [ + self + publish: 'Hello' onQueueNamed: self queueName; + publish: 'World' onQueueNamed: self queueName + ]. self assert: reversedTexts size equals: 2; @@ -85,14 +90,8 @@ RabbitMQTextReverserTest >> testProcessingMessages [ { #category : 'tests' } RabbitMQTextReverserTest >> testProcessingOneMessage [ - workerProcess resume. + self resumeWorkerDuring: [ self publish: 'Hello' onQueueNamed: self queueName ]. - Processor yield. - self wait. - - self publish: 'Hello' onQueueNamed: self queueName. - - self wait. self withTheOnlyOneIn: reversedTexts do: [ :text | self assert: text equals: 'olleH' ] ] @@ -103,7 +102,7 @@ RabbitMQTextReverserTest >> wait [ ] { #category : 'private' } -RabbitMQTextReverserTest >> withLocalhostConnectionDo: block [ +RabbitMQTextReverserTest >> withLocalhostConnectionDo: aBlock [ | connection | @@ -113,7 +112,7 @@ RabbitMQTextReverserTest >> withLocalhostConnectionDo: block [ connection whenConnected: [ - block value: connection. + aBlock value: connection. connection close ] whenNot: [ :error | self fail: error messageText ] diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 1e6991c7..f1080a7e 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -14,6 +14,9 @@ task during a short HTTP request window. Class { #name : 'RabbitMQWorker', #superclass : 'RabbitMQClient', + #instVars : [ + 'channel' + ], #category : 'Ansible-RabbitMQ', #package : 'Ansible-RabbitMQ' } @@ -25,6 +28,36 @@ RabbitMQWorker class >> isAbstract [ ^ self = RabbitMQWorker ] +{ #category : 'private' } +RabbitMQWorker >> configureChannel [ + + channel queueDeclare: self queueName durable: true. + channel prefetchCount: 1. + channel consumeFrom: self queueName applying: [ :message | + self process: message body. + channel basicAck: message method deliveryTag + ] +] + +{ #category : 'private' } +RabbitMQWorker >> ensureChannelOpen [ + + | reconnect | + + reconnect := [ + self ensureConnectedAndOpen. + channel := connection createChannel + ]. + channel ifNil: reconnect ifNotNil: [ channel whenOpenDo: [ ] whenClosedDo: reconnect ] +] + +{ #category : 'private' } +RabbitMQWorker >> logDisconnectionDueTo: anError [ + + LaunchpadLogRecord emitError: + ( 'RabbitMQClient disconnected: <1s>' expandMacrosWith: anError messageText ) +] + { #category : 'private' } RabbitMQWorker >> process: payload [ @@ -42,18 +75,15 @@ RabbitMQWorker >> queueName [ { #category : 'private' } RabbitMQWorker >> startProcessing [ - | channel reconnect | + self + ensureChannelOpen; + configureChannel. - reconnect := [ - self ensureConnectedAndOpen. - channel := connection createChannel]. - channel ifNil: reconnect ifNotNil: [channel whenOpenDo: [] whenClosedDo: reconnect]. - - channel queueDeclare: self queueName durable: true. - channel prefetchCount: 1. - channel consumeFrom: self queueName applying: [ :message | - self process: message body. - channel basicAck: message method deliveryTag - ]. - [ connection waitForEvent ] repeat + [[ connection waitForEvent ] + on: AmqpDisconnectedError + do: [ :error | + self + logDisconnectionDueTo: error; + ensureChannelOpen; + configureChannel]] repeat ] From 069d8248f525155fa7d0ae2dca82dcaeb6cb6179 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Tue, 16 Jul 2024 16:14:24 -0300 Subject: [PATCH 09/65] :test_tube: Add rabbit disconnection test --- .../AmqpChannel.class.st | 12 +++++++ .../AmqpChannelHandler.class.st | 32 ++++++++++-------- .../AmqpConnection.class.st | 12 ++++++- .../RabbitMQPublisherTest.class.st | 11 +++++-- .../RabbitMQTextReverser.class.st | 6 ++++ .../RabbitMQTextReverserTest.class.st | 33 +++++++++++++++++++ .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 19 +++++++---- 7 files changed, 101 insertions(+), 24 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpChannel.class.st b/source/Ansible-Protocol-Core/AmqpChannel.class.st index 8120d891..35ebc582 100644 --- a/source/Ansible-Protocol-Core/AmqpChannel.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannel.class.st @@ -347,6 +347,18 @@ AmqpChannel >> prefetchCount: prefetchCount prefetchSize: prefetchSize global: a global: aBoolean ) ] +{ #category : 'AMQP receiving messages' } +AmqpChannel >> printOn: aStream [ + + aStream nextPutAll: ( 'AMPQ Channel <1p> on connection <2p> (<3s>)' + expandMacrosWith: handler channelNumber + with: handler connection + with: + ( self + whenOpenDo: [ 'Open' ] + whenClosedDo: [ :reason | 'Closed due <1p>' expandMacrosWith: reason ] ) ) +] + { #category : 'AMQP private' } AmqpChannel >> protocolClass [ diff --git a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st index a9945890..f9ee09bb 100644 --- a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st @@ -163,20 +163,26 @@ AmqpChannelHandler >> waitForReply: acceptableReplies [ acceptableReplies ifEmpty: [ ^ nil ]. i := 1. - [ [ i > inbound size ] - whileTrue: [ self ensureOpen. - connection mainLoopCycle + [ + [ i > inbound size ] whileTrue: [ + | currentConnection | + + self ensureOpen. + currentConnection := connection mainLoopCycle. + currentConnection socketConnectionStatus + whenConnected: [ ] + whenNot: [ :error | AmqpDisconnectedError signal: error ]]. + [ i <= inbound size ] whileTrue: [ + | cmd | + + cmd := inbound at: i. + ( acceptableReplies includes: cmd method class ) ifTrue: [ + LogRecord emitInfo: ' estoy en el whileTrue del cmd donde deberia de regresar el command'. + inbound removeAt: i. + ^ cmd ]. - [ i <= inbound size ] - whileTrue: [ | cmd | - - cmd := inbound at: i. - ( acceptableReplies includes: cmd method class ) - ifTrue: [ inbound removeAt: i. - ^ cmd - ]. - i := i + 1 - ] + i := i + 1 + ] ] repeat ] diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index beb8bea4..8e4ab39e 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -352,9 +352,13 @@ AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber ifConnectionClos | ch | + LogRecord emitInfo: 'Llegue al RPC de la conexion'. + ch := self ensureChannel: aChannelNumber. self sendMethod: aRequestMethod onChannel: aChannelNumber. - ^ ch waitForReply: aRequestMethod acceptableResponseClasses + ^ [ ch waitForReply: aRequestMethod acceptableResponseClasses ] + on: ConnectionClosed + do: [ LogRecord emitInfo: 'SE CERRO LA CONEXION EN EL WAIT FOR REPLY' ] ] { #category : 'connection-handling' } @@ -432,6 +436,12 @@ AmqpConnection >> setChannel: aChannelNumber to: aChannelHandler [ channels at: aChannelNumber put: aChannelHandler ] +{ #category : 'connection-handling' } +AmqpConnection >> socketConnectionStatus [ + + ^ socketConnectionStatus +] + { #category : 'private-opening' } AmqpConnection >> startConnection [ diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st index c3aa0127..4cae4914 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st @@ -25,7 +25,7 @@ RabbitMQPublisherTest >> configureConnection: aBuilder [ aBuilder password: 'guest' ] -{ #category : 'private - accessing' } +{ #category : 'running' } RabbitMQPublisherTest >> defaultRabbitMQPublisher [ ^ RabbitMQPublisher configuredBy: [ :options | @@ -194,9 +194,14 @@ RabbitMQPublisherTest >> testDebuggingLogsTurnedOff [ { #category : 'running' } RabbitMQPublisherTest >> testPublishingMessageWhenChannelIsTemporallyLost [ + |logger| + + logger := MemoryLogger new. + self resumeWorkerDuring: [ - publisher channel close. - self publish: #( 'test channel restored' ) onQueueNamed: self queueName + logger runDuring: [ publisher channel close. + self publish: #( 'test channel restored' ) onQueueNamed: self queueName ] + ]. self diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index 4297922f..1e814075 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -14,6 +14,12 @@ RabbitMQTextReverser class >> workingWith: aTestCase [ ^self new initializeWorkingWith: aTestCase ] +{ #category : 'accessing' } +RabbitMQTextReverser >> channel [ + + ^ channel +] + { #category : 'private' } RabbitMQTextReverser >> configureConnection: builder [ diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st index 8362ff34..cfa4f883 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st @@ -13,6 +13,12 @@ Class { #package : 'Ansible-RabbitMQ-Tests' } +{ #category : 'accessing' } +RabbitMQTextReverserTest class >> defaultTimeLimit [ + + ^ 60 seconds +] + { #category : 'private' } RabbitMQTextReverserTest >> publish: aText onQueueNamed: aQueueName [ @@ -72,6 +78,33 @@ RabbitMQTextReverserTest >> tearDown [ super tearDown ] +{ #category : 'tests' } +RabbitMQTextReverserTest >> testProcessingMessageWhenChannelIsTemporallyLost [ + + | logger | + + logger := MemoryLogger new. + + self resumeWorkerDuring: [ "self + publish: 'Hello' onQueueNamed: self queueName; + publish: 'World' onQueueNamed: self queueName. + + self wait. + + self + assert: reversedTexts size equals: 2; + assert: reversedTexts first equals: 'olleH'; + assert: reversedTexts last equals: 'dlroW'." + logger runDuring: [ worker stop]. + + self publish: 'Test channel restored' onQueueNamed: self queueName + ]. + + self + assert: reversedTexts size equals: 3; + assert: reversedTexts last equals: 'derotser lennahc tseT' +] + { #category : 'tests' } RabbitMQTextReverserTest >> testProcessingMessages [ diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index f1080a7e..373c0722 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -46,9 +46,17 @@ RabbitMQWorker >> ensureChannelOpen [ reconnect := [ self ensureConnectedAndOpen. + LogRecord emitInfo: 'Pase el ensureConnected para crear el channel'. channel := connection createChannel ]. - channel ifNil: reconnect ifNotNil: [ channel whenOpenDo: [ ] whenClosedDo: reconnect ] + + + channel ifNil: reconnect ifNotNil: [ + LogRecord emitInfo: channel printString. + channel whenOpenDo: [ ] whenClosedDo: reconnect + ]. + + self configureChannel ] { #category : 'private' } @@ -75,15 +83,12 @@ RabbitMQWorker >> queueName [ { #category : 'private' } RabbitMQWorker >> startProcessing [ - self - ensureChannelOpen; - configureChannel. + self ensureChannelOpen. [[ connection waitForEvent ] - on: AmqpDisconnectedError + on: AmqpDisconnectedError,ConnectionClosed do: [ :error | self logDisconnectionDueTo: error; - ensureChannelOpen; - configureChannel]] repeat + ensureChannelOpen]] repeat ] From 57f9bfe7bf9255d5e5cad59712f297e5f044c061 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Mon, 22 Jul 2024 13:39:50 -0300 Subject: [PATCH 10/65] :recycle: RabbitMQ client, worker , publisher refactors --- source/Ansible-RabbitMQ/RabbitMQClient.class.st | 2 +- source/Ansible-RabbitMQ/RabbitMQPublisher.class.st | 13 +++++++------ source/Ansible-RabbitMQ/RabbitMQWorker.class.st | 13 ++++--------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index f1222dfe..ba4340ec 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -31,7 +31,7 @@ RabbitMQClient >> configureConnection: builder [ { #category : 'private - configuring' } RabbitMQClient >> connectivityErrors [ - ^ NetworkError + ^ NetworkError , ConnectionClosed , AmqpDisconnectedError ] { #category : 'private' } diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index efdaa27a..2c01d3ca 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -29,16 +29,16 @@ RabbitMQPublisher class >> configuredBy: aConfigurationAction [ ^ self new initializeConfiguredBy: options ] -{ #category : 'accessing' } +{ #category : 'private - accessing' } RabbitMQPublisher >> channel [ - ^channel + ^ channel ] { #category : 'private - accessing' } RabbitMQPublisher >> connectivityErrors [ - ^ AmqpDisconnectedError , NetworkError + ^ NetworkError , ConnectionClosed , AmqpDisconnectedError ] { #category : 'private - connecting' } @@ -47,9 +47,10 @@ RabbitMQPublisher >> ensureChannelOpen [ | reconnect | reconnect := [ - self ensureConnectedAndOpen. - channel := connection createChannel]. - channel ifNil: reconnect ifNotNil: [channel whenOpenDo: [] whenClosedDo: reconnect] + self ensureConnectedAndOpen. + channel := connection createChannel + ]. + channel ifNil: reconnect ifNotNil: [ channel whenOpenDo: [ ] whenClosedDo: reconnect ] ] { #category : 'private - connecting' } diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 373c0722..881037af 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -46,15 +46,10 @@ RabbitMQWorker >> ensureChannelOpen [ reconnect := [ self ensureConnectedAndOpen. - LogRecord emitInfo: 'Pase el ensureConnected para crear el channel'. channel := connection createChannel ]. - - channel ifNil: reconnect ifNotNil: [ - LogRecord emitInfo: channel printString. - channel whenOpenDo: [ ] whenClosedDo: reconnect - ]. + channel ifNil: reconnect ifNotNil: [ channel whenOpenDo: [ ] whenClosedDo: reconnect ]. self configureChannel ] @@ -62,8 +57,8 @@ RabbitMQWorker >> ensureChannelOpen [ { #category : 'private' } RabbitMQWorker >> logDisconnectionDueTo: anError [ - LaunchpadLogRecord emitError: - ( 'RabbitMQClient disconnected: <1s>' expandMacrosWith: anError messageText ) + LogRecord emitError: + ( 'RabbitMQClient disconnected due to <1s>' expandMacrosWith: anError messageText ) ] { #category : 'private' } @@ -86,7 +81,7 @@ RabbitMQWorker >> startProcessing [ self ensureChannelOpen. [[ connection waitForEvent ] - on: AmqpDisconnectedError,ConnectionClosed + on: self connectivityErrors do: [ :error | self logDisconnectionDueTo: error; From 9fcb756758d53cca811edd4b3c335655172734d0 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Mon, 22 Jul 2024 13:40:47 -0300 Subject: [PATCH 11/65] :recycle: AmqpChannel refactors --- .../AmqpChannel.class.st | 16 +++++++ .../AmqpChannelHandler.class.st | 8 ++-- .../AmqpConnection.class.st | 43 +++++++++---------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpChannel.class.st b/source/Ansible-Protocol-Core/AmqpChannel.class.st index 35ebc582..176b1a07 100644 --- a/source/Ansible-Protocol-Core/AmqpChannel.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannel.class.st @@ -311,6 +311,22 @@ AmqpChannel >> handleChannelClose: cmd [ onChannel: handler channelNumber "TODO: notify callbacks of closure" ] +{ #category : 'AMQP closing' } +AmqpChannel >> hardCloseDescribedWith: aDescription [ + + | c | + + handler closeReason + ifNil: [ c := protocolVersion channelCloseMethod new + replyCode: protocolVersion internalError; + replyText: 'Abnormal disconnection'; + classId: 0; + methodId: 0. + handler rpc: c. + handler internalClose: c + ] +] + { #category : 'initialization' } AmqpChannel >> initializeUsing: aChannelHandler [ diff --git a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st index f9ee09bb..98de956c 100644 --- a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st @@ -171,13 +171,13 @@ AmqpChannelHandler >> waitForReply: acceptableReplies [ currentConnection := connection mainLoopCycle. currentConnection socketConnectionStatus whenConnected: [ ] - whenNot: [ :error | AmqpDisconnectedError signal: error ]]. + whenNot: [ :error | AmqpDisconnectedError signal: error ] + ]. [ i <= inbound size ] whileTrue: [ | cmd | cmd := inbound at: i. ( acceptableReplies includes: cmd method class ) ifTrue: [ - LogRecord emitInfo: ' estoy en el whileTrue del cmd donde deberia de regresar el command'. inbound removeAt: i. ^ cmd ]. @@ -189,5 +189,7 @@ AmqpChannelHandler >> waitForReply: acceptableReplies [ { #category : 'accessing' } AmqpChannelHandler >> whenOpenDo: aBlock whenClosedDo: aClosedHandler [ - ^closeReason ifNil: aBlock ifNotNil: aClosedHandler + ^ closeReason + ifNil: [ connection whenConnected: aBlock whenNot: aClosedHandler ] + ifNotNil: aClosedHandler ] diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 8e4ab39e..600bcc7b 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -78,11 +78,11 @@ AmqpConnection >> codec [ { #category : 'accessing' } AmqpConnection >> connectionPairsDescription [ - ^ '<1s>:<2s>-><3s>:<4s>' - expandMacrosWith: socket peerName asString - with: socket port asString + ^ '<1p>:<2p>-><3s>:<4p>' + expandMacrosWith: socket peerName + with: socket port with: hostname - with: portNumber asString + with: portNumber ] { #category : 'connection-handling' } @@ -352,13 +352,9 @@ AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber ifConnectionClos | ch | - LogRecord emitInfo: 'Llegue al RPC de la conexion'. - ch := self ensureChannel: aChannelNumber. self sendMethod: aRequestMethod onChannel: aChannelNumber. - ^ [ ch waitForReply: aRequestMethod acceptableResponseClasses ] - on: ConnectionClosed - do: [ LogRecord emitInfo: 'SE CERRO LA CONEXION EN EL WAIT FOR REPLY' ] + ^ ch waitForReply: aRequestMethod acceptableResponseClasses ] { #category : 'connection-handling' } @@ -445,21 +441,24 @@ AmqpConnection >> socketConnectionStatus [ { #category : 'private-opening' } AmqpConnection >> startConnection [ - | response | + | response nextFrame | + + nextFrame := self nextFrame. - response := credentials responseFor: self nextFrame method. - response - ifNil: [ AmqpDisconnectedError - signal: 'No acceptable SASL mechanism for the given credentials' ]. + nextFrame ifNil: [ + self hardCloseDescribedWith: 'Failed retrieving next frame'. + ^ self]. + + response := credentials responseFor: nextFrame method. + response ifNil: [ + AmqpDisconnectedError signal: 'No acceptable SASL mechanism for the given credentials' ]. self - sendMethod: - (self protocolClass connectionStartOkMethod new - clientProperties: - (Dictionary new - at: 'product' put: 'RabbitMQ Smalltalk'; - yourself); - mechanism: response key; - response: response value) + sendMethod: ( self protocolClass connectionStartOkMethod new + clientProperties: ( Dictionary new + at: 'product' put: 'RabbitMQ Smalltalk'; + yourself ); + mechanism: response key; + response: response value ) onChannel: 0. credentials := nil ] From 6d049f2d2382f62bf19be4323faf01258d14ddb7 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Mon, 22 Jul 2024 13:41:32 -0300 Subject: [PATCH 12/65] :white_check_mark: Add RabbitMQWorker reconnection test --- .../RabbitMQTextReverserTest.class.st | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st index cfa4f883..f186e534 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st @@ -13,10 +13,39 @@ Class { #package : 'Ansible-RabbitMQ-Tests' } -{ #category : 'accessing' } -RabbitMQTextReverserTest class >> defaultTimeLimit [ +{ #category : 'private' } +RabbitMQTextReverserTest >> closeAllConnectionsOf: aRabbitmqContainerId for: aUsername because: aCloseReason [ - ^ 60 seconds + ^ OSPlatform current runCommand: + ( 'docker exec <1s> rabbitmqctl close_all_user_connections <2s> <3s>' + expandMacrosWith: aRabbitmqContainerId + with: aUsername + with: aCloseReason ) +] + +{ #category : 'private' } +RabbitMQTextReverserTest >> closeAllUserConnections [ + + | rabbitmqContainerId closeReason | + + rabbitmqContainerId := self rabbitMQContainerID. + + closeReason := 'CloseConnectionsTest'. + + rabbitmqContainerId isEmpty + then: [ Error signal: 'Could not find a running RabbitMQ container.' ] + otherwise: [ + self + closeAllConnectionsOf: rabbitmqContainerId + for: self defaultRabbitMQWorkerUsername + because: closeReason + ] +] + +{ #category : 'private' } +RabbitMQTextReverserTest >> defaultRabbitMQWorkerUsername [ + + ^ AmqpConnectionBuilder usingAMQP091Protocol credentials username ] { #category : 'private' } @@ -41,6 +70,12 @@ RabbitMQTextReverserTest >> queueName [ ^ 'tasks-for-' , testSelector ] +{ #category : 'private' } +RabbitMQTextReverserTest >> rabbitMQContainerID [ + + ^ ( OSPlatform current resultOfCommand: 'docker ps -q --filter "name=rabbitmq"' ) trim +] + { #category : 'private' } RabbitMQTextReverserTest >> resumeWorkerDuring: aBlock [ @@ -79,13 +114,14 @@ RabbitMQTextReverserTest >> tearDown [ ] { #category : 'tests' } -RabbitMQTextReverserTest >> testProcessingMessageWhenChannelIsTemporallyLost [ +RabbitMQTextReverserTest >> testProcessingMessageWhenConnectionIsTemporallyLost [ | logger | logger := MemoryLogger new. - self resumeWorkerDuring: [ "self + self resumeWorkerDuring: [ + self publish: 'Hello' onQueueNamed: self queueName; publish: 'World' onQueueNamed: self queueName. @@ -94,15 +130,28 @@ RabbitMQTextReverserTest >> testProcessingMessageWhenChannelIsTemporallyLost [ self assert: reversedTexts size equals: 2; assert: reversedTexts first equals: 'olleH'; - assert: reversedTexts last equals: 'dlroW'." - logger runDuring: [ worker stop]. - - self publish: 'Test channel restored' onQueueNamed: self queueName + assert: reversedTexts last equals: 'dlroW'. + + logger runDuring: [ + self + closeAllUserConnections; + wait + ]. + self publish: 'Test connection restored' onQueueNamed: self queueName ]. + self assert: reversedTexts size equals: 3; - assert: reversedTexts last equals: 'derotser lennahc tseT' + assert: reversedTexts last equals: 'derotser noitcennoc tseT'. + + self assert: logger recordings size equals: 2. + + self assert: ( '*[ERROR] RabbitMQClient disconnected due to Connection closed' match: + ( logger recordings at: 1 ) printString ). + + self assert: ( '* [INFO] AMQP connection *->localhost:5672 established successfully' match: + ( logger recordings at: 2 ) printString ) ] { #category : 'tests' } From de5781da26792a08e4ffc53f20f7ef560d62a643 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 22 Jul 2024 16:51:47 -0300 Subject: [PATCH 13/65] upgrade dependencies --- source/BaselineOfAnsible/BaselineOfAnsible.class.st | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/BaselineOfAnsible/BaselineOfAnsible.class.st b/source/BaselineOfAnsible/BaselineOfAnsible.class.st index 2a0facf3..4cbec742 100644 --- a/source/BaselineOfAnsible/BaselineOfAnsible.class.st +++ b/source/BaselineOfAnsible/BaselineOfAnsible.class.st @@ -37,15 +37,15 @@ BaselineOfAnsible >> setUpDependencies: spec [ project: 'XMLParser-Core' copyFrom: 'XMLParser' with: [ spec loads: #( 'Core' ) ]. spec - baseline: 'Launchpad' with: [ spec repository: 'github://ba-st/Launchpad:v4' ]; + baseline: 'Launchpad' with: [ spec repository: 'github://ba-st/Launchpad:v6' ]; project: 'Launchpad-Deployment' copyFrom: 'Launchpad' with: [ spec loads: 'Deployment' ]. spec - baseline: 'Buoy' with: [ spec repository: 'github://ba-st/Buoy:v6' ]; + baseline: 'Buoy' with: [ spec repository: 'github://ba-st/Buoy:v8' ]; project: 'Buoy-SUnit' copyFrom: 'Buoy' with: [ spec loads: 'Dependent-SUnit-Extensions' ]. spec - baseline: 'Hyperspace' with: [ spec repository: 'github://ba-st/Hyperspace:v4' ]; + baseline: 'Hyperspace' with: [ spec repository: 'github://ba-st/Hyperspace:v6' ]; project: 'Hyperspace-Deployment' copyFrom: 'Hyperspace' with: [ spec loads: 'Deployment' ] ] From 37809cdff687c484da81fa5b77bab2ae015edaaf Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 22 Jul 2024 17:46:57 -0300 Subject: [PATCH 14/65] add pharo 12 to the CI actions --- .github/workflows/loading-groups.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/loading-groups.yml b/.github/workflows/loading-groups.yml index 2b0f9caa..aef9df16 100644 --- a/.github/workflows/loading-groups.yml +++ b/.github/workflows/loading-groups.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - smalltalk: [ Pharo64-11, Pharo64-10, Pharo64-9.0, Pharo64-8.0 ] + smalltalk: [ Pharo64-12, Pharo64-11, Pharo64-10, Pharo64-9.0, Pharo64-8.0 ] load-spec: [ deployment, tests, development, tools ] name: ${{ matrix.smalltalk }} + ${{ matrix.load-spec }} services: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index a90a34d4..b076ea00 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - smalltalk: [ Pharo64-11, Pharo64-10, Pharo64-9.0, Pharo64-8.0 ] + smalltalk: [ Pharo64-12, Pharo64-11, Pharo64-10, Pharo64-9.0, Pharo64-8.0 ] name: ${{ matrix.smalltalk }} services: rabbitmq: From e555b7d74acb679ccc3ec920ddd121621da25a8b Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Thu, 25 Jul 2024 12:31:09 -0300 Subject: [PATCH 15/65] :art: Add support to handle nextFrame --- .../AmqpConnection.class.st | 79 ++++++++++--------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 600bcc7b..63d5c0bc 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -226,15 +226,10 @@ AmqpConnection >> internalClose: method [ { #category : 'connection-handling' } AmqpConnection >> mainLoopCycle [ - | frame | - - frame := self nextFrame. - frame ifNil: [ - self hardCloseDescribedWith: 'Failed retrieving next frame'. - ^ self - ]. - ( frame isKindOf: AmqpHeartbeatFrame ) ifFalse: [ - ( channels at: frame channelNumber ) handleFrame: frame ] + self withNextFrameDo: [ :nextFrame | + ( nextFrame isKindOf: AmqpHeartbeatFrame ) ifFalse: [ + ( channels at: nextFrame channelNumber ) handleFrame: nextFrame ] + ] ] { #category : 'connection-handling' } @@ -441,39 +436,36 @@ AmqpConnection >> socketConnectionStatus [ { #category : 'private-opening' } AmqpConnection >> startConnection [ - | response nextFrame | - - nextFrame := self nextFrame. - - nextFrame ifNil: [ - self hardCloseDescribedWith: 'Failed retrieving next frame'. - ^ self]. + self withNextFrameDo: [ :nextFrame | + | response | - response := credentials responseFor: nextFrame method. - response ifNil: [ - AmqpDisconnectedError signal: 'No acceptable SASL mechanism for the given credentials' ]. - self - sendMethod: ( self protocolClass connectionStartOkMethod new - clientProperties: ( Dictionary new - at: 'product' put: 'RabbitMQ Smalltalk'; - yourself ); - mechanism: response key; - response: response value ) - onChannel: 0. - credentials := nil + response := credentials responseFor: nextFrame method. + response ifNil: [ + AmqpDisconnectedError signal: 'No acceptable SASL mechanism for the given credentials' ]. + self + sendMethod: ( self protocolClass connectionStartOkMethod new + clientProperties: ( Dictionary new + at: 'product' put: 'RabbitMQ Smalltalk'; + yourself ); + mechanism: response key; + response: response value ) + onChannel: 0. + credentials := nil + ] ] { #category : 'private-opening' } AmqpConnection >> tuneConnection [ - parameters applyServerSettings: self nextFrame method. - self - sendMethod: - (self protocolClass connectionTuneOkMethod new - channelMax: parameters channelMax; - frameMax: parameters frameMax; - heartbeat: parameters heartbeat) - onChannel: 0 + self withNextFrameDo: [ :nextFrame | + parameters applyServerSettings: nextFrame method. + self + sendMethod: ( self protocolClass connectionTuneOkMethod new + channelMax: parameters channelMax; + frameMax: parameters frameMax; + heartbeat: parameters heartbeat ) + onChannel: 0 + ] ] { #category : 'accessing' } @@ -503,3 +495,18 @@ AmqpConnection >> whenOpen: aBlock whenNot: aClosedHandler [ ^isOpen then: aBlock otherwise: aClosedHandler ] + +{ #category : 'connection-handling' } +AmqpConnection >> withNextFrameDo: aBlock [ + + | nextFrame | + + nextFrame := self nextFrame. + + nextFrame + ifNil: [ + self hardCloseDescribedWith: 'Failed retrieving next frame'. + ^ self + ] + ifNotNil: aBlock +] From c5081d80cac2f4f7825d80c4c4938d06b3657fc1 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Thu, 25 Jul 2024 13:35:45 -0300 Subject: [PATCH 16/65] :art: Add waitForReply handle --- source/Ansible-Protocol-Core/AmqpConnection.class.st | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 63d5c0bc..cdeee657 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -349,7 +349,9 @@ AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber ifConnectionClos ch := self ensureChannel: aChannelNumber. self sendMethod: aRequestMethod onChannel: aChannelNumber. - ^ ch waitForReply: aRequestMethod acceptableResponseClasses + ^ [ ch waitForReply: aRequestMethod acceptableResponseClasses ] + on: AmqpDisconnectedError , ConnectionClosed + do: [ :signal | aBlock cull: signal ] ] { #category : 'connection-handling' } From 04516d3d2d81394a7f030c036f775bdf7892eaa6 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Fri, 26 Jul 2024 11:33:57 -0300 Subject: [PATCH 17/65] :test_tube: Updated test to allow Pharo retrocompatibility --- .../RabbitMQPublisherTest.class.st | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st index 4cae4914..0866d40c 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st @@ -115,30 +115,23 @@ RabbitMQPublisherTest >> testCannotStartBecauseNotFoundARabbitMQService [ | anotherPublisher logger expectedErrorDescription | logger := MemoryLogger new. - - anotherPublisher := - RabbitMQPublisher configuredBy: [:options | - options - at: #hostname put: 'looney-tunes.com'; - at: #port put: 1234; - at: #username put: 'bugs'; - at: #password put: 'bunny'; - at: #maximumConnectionAttemps put: 1; - at: #timeSlotBetweenConnectionRetriesInMs put: 1]. + + anotherPublisher := RabbitMQPublisher configuredBy: [ :options | + options + at: #hostname put: 'looney-tunes.com'; + at: #port put: 1234; + at: #username put: 'bugs'; + at: #password put: 'bunny'; + at: #maximumConnectionAttemps put: 1; + at: #timeSlotBetweenConnectionRetriesInMs put: 1 + ]. expectedErrorDescription := 'connection closed while sending data'. - - logger runDuring: [ - self - should: [anotherPublisher start] - raise: ConnectionClosed - description: expectedErrorDescription]. - self - assert: logger recordings size equals: 3; - assert: ( - logger recordings second printString endsWith: - (#'Attempt #1/1 to connect to RabbitMQ failed: <1s>' expandMacrosWith: expectedErrorDescription)) + logger runDuring: [ self shouldFix: [ anotherPublisher start ] ]. + + self assert: ( logger recordings anySatisfy: [ :recording | + recording printString includesSubstring: #'Attempt #1/1 to connect to RabbitMQ failed' ] ) ] { #category : 'running' } From 569b1a80728dded2319de3d19f954cfa59e5c6dc Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Fri, 26 Jul 2024 11:35:08 -0300 Subject: [PATCH 18/65] :memo: Added Pharo 12 support --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bf14b8e3..43474550 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ Ansible is a AMQP client library for Smalltalk supporting 0-8 and 0-9-1 [![Pharo 8.0](https://img.shields.io/badge/Pharo-8.0-informational)](https://pharo.org) [![Pharo 9.0](https://img.shields.io/badge/Pharo-9.0-informational)](https://pharo.org) [![Pharo 10](https://img.shields.io/badge/Pharo-10-informational)](https://pharo.org) -[![Pharo 11](https://img.shields.io/badge/Pharo-10-informational)](https://pharo.org) +[![Pharo 11](https://img.shields.io/badge/Pharo-11-informational)](https://pharo.org) +[![Pharo 12](https://img.shields.io/badge/Pharo-12-informational)](https://pharo.org) An [Ansible](https://en.wikipedia.org/wiki/Ansible) is a fictional device capable of near-instantaneous communication. It can send and receive message From ba135bebea227589de0949ac7135d5950a623bf1 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Fri, 26 Jul 2024 15:58:48 -0300 Subject: [PATCH 19/65] Update references of LaunchpadLogRecord to LogRecord --- source/Ansible-RabbitMQ/RabbitMQClient.class.st | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index ba4340ec..1b484f08 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -67,7 +67,7 @@ RabbitMQClient >> initializeConnection [ { #category : 'private - connecting' } RabbitMQClient >> logFailedConnectionAttempt: attemptNumber dueTo: error [ - LaunchpadLogRecord emitError: ( 'Attempt #<1p>/<2p> to connect to RabbitMQ failed: <3s>' + LogRecord emitError: ( 'Attempt #<1p>/<2p> to connect to RabbitMQ failed: <3s>' expandMacrosWith: attemptNumber with: self retryCount + 1 with: error messageText ) @@ -77,7 +77,7 @@ RabbitMQClient >> logFailedConnectionAttempt: attemptNumber dueTo: error [ RabbitMQClient >> openConnection [ self withSuccessfulConnectionDo: [ :succesfulConnection | - LaunchpadLogRecord emitInfo: 'Connecting to RabbitMQ' during: [ + LogRecord emitInfo: 'Connecting to RabbitMQ' during: [ self try: [ succesfulConnection open ] onConnectivityErrorDo: [ :attemptNumber :error | @@ -104,11 +104,11 @@ RabbitMQClient >> start [ self openConnection. connection whenConnected: [ - LaunchpadLogRecord emitInfo: 'Connected to RabbitMQ'. + LogRecord emitInfo: 'Connected to RabbitMQ'. self startProcessing ] whenNot: [ :error | - LaunchpadLogRecord emitError: + LogRecord emitError: ( 'Cannot connect to RabbitMQ, <1s>' expandMacrosWith: error messageText ). AmqpDisconnectedError signal: error messageText ] @@ -125,7 +125,7 @@ RabbitMQClient >> stop [ connection whenConnected: [ connection close ] - whenNot: [ LaunchpadLogRecord emitWarning: 'RabbitMQ connection was already closed.' ] + whenNot: [ LogRecord emitWarning: 'RabbitMQ connection was already closed.' ] ] { #category : 'private - connecting' } @@ -146,7 +146,7 @@ RabbitMQClient >> withSuccessfulConnectionDo: aBlock [ try: [ connection whenConnected: [ aBlock value: connection ] whenNot: [ :error | error signal ] ] onConnectivityErrorDo: [ :attemptNumber :error | self logFailedConnectionAttempt: attemptNumber dueTo: error. - LaunchpadLogRecord emitWarning: 'Reconnecting socket to RabbitMQ'. + LogRecord emitWarning: 'Reconnecting socket to RabbitMQ'. connection initializeSocketConnection ] ] From 4838473dbaab27ef69ce7f8f20d5c26f5376ea79 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Sat, 27 Jul 2024 23:37:58 -0300 Subject: [PATCH 20/65] Refactor the RabbitMQClient hierarchy to make the worker and publisher subclasses of it. Push up the publisher's logic to reconnect to RabbitMQClient --- .../RabbitMQTextReverser.class.st | 12 +- .../Ansible-RabbitMQ/RabbitMQClient.class.st | 112 +++++++---------- .../RabbitMQPublisher.class.st | 113 +----------------- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 64 +++++----- 4 files changed, 88 insertions(+), 213 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index 1e814075..c015fab5 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -20,17 +20,13 @@ RabbitMQTextReverser >> channel [ ^ channel ] -{ #category : 'private' } -RabbitMQTextReverser >> configureConnection: builder [ - - -] - { #category : 'initialization' } RabbitMQTextReverser >> initializeWorkingWith: aTestCase [ - testCase := aTestCase. - self initializeConnection + testCase := aTestCase. + self initializeConfiguredBy: ( Dictionary new + at: #hostname put: 'localhost'; + yourself ) ] { #category : 'private' } diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 1b484f08..24ba42ed 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -9,7 +9,9 @@ Class { #superclass : 'Object', #instVars : [ 'connection', - 'builder' + 'builder', + 'channel', + 'options' ], #category : 'Ansible-RabbitMQ', #package : 'Ansible-RabbitMQ' @@ -23,15 +25,21 @@ RabbitMQClient class >> isAbstract [ ] { #category : 'private - configuring' } -RabbitMQClient >> configureConnection: builder [ +RabbitMQClient >> connectivityErrors [ - self subclassResponsibility + ^ NetworkError , ConnectionClosed , AmqpDisconnectedError ] -{ #category : 'private - configuring' } -RabbitMQClient >> connectivityErrors [ +{ #category : 'private' } +RabbitMQClient >> ensureChannelOpen [ - ^ NetworkError , ConnectionClosed , AmqpDisconnectedError + | reconnect | + + reconnect := [ + self ensureConnectedAndOpen. + channel := connection createChannel + ]. + channel ifNil: reconnect ifNotNil: [ channel whenOpenDo: [ ] whenClosedDo: reconnect ] ] { #category : 'private' } @@ -57,11 +65,14 @@ RabbitMQClient >> ensureConnectedAndOpen [ ] { #category : 'initialization' } -RabbitMQClient >> initializeConnection [ - - builder := AmqpConnectionBuilder usingAMQP091Protocol. - self configureConnection: builder. - connection := builder build +RabbitMQClient >> initializeConfiguredBy: anOptionsDictionary [ + + options := anOptionsDictionary. + builder := AmqpConnectionBuilder usingAMQP091Protocol. + builder hostname: ( options at: #hostname ). + builder portNumber: ( options at: #port ifAbsent: [ 5672 ] ). + builder username: ( options at: #username ifAbsent: [ 'guest' ] ). + builder password: ( options at: #password ifAbsent: [ 'guest' ] ) ] { #category : 'private - connecting' } @@ -69,84 +80,53 @@ RabbitMQClient >> logFailedConnectionAttempt: attemptNumber dueTo: error [ LogRecord emitError: ( 'Attempt #<1p>/<2p> to connect to RabbitMQ failed: <3s>' expandMacrosWith: attemptNumber - with: self retryCount + 1 + with: self retryCount with: error messageText ) ] -{ #category : 'private - connecting' } -RabbitMQClient >> openConnection [ - - self withSuccessfulConnectionDo: [ :succesfulConnection | - LogRecord emitInfo: 'Connecting to RabbitMQ' during: [ - self - try: [ succesfulConnection open ] - onConnectivityErrorDo: [ :attemptNumber :error | - self logFailedConnectionAttempt: attemptNumber dueTo: error ] - ] - ] -] - { #category : 'private - configuring' } -RabbitMQClient >> options [ +RabbitMQClient >> retryCount [ - ^ Dictionary new + ^ options at: #maximumConnectionAttemps ifAbsent: [ 3 ] ] -{ #category : 'private - configuring' } -RabbitMQClient >> retryCount [ +{ #category : 'private - testing' } +RabbitMQClient >> shouldLogDebuggingInfo [ - ^ self options at: #retryCount ifAbsent: [ 2 ] + ^ options at: #enableDebuggingLogs ifAbsent: [ false ] ] { #category : 'controlling' } RabbitMQClient >> start [ - self openConnection. - connection - whenConnected: [ - LogRecord emitInfo: 'Connected to RabbitMQ'. - self startProcessing - ] - whenNot: [ :error | - LogRecord emitError: - ( 'Cannot connect to RabbitMQ, <1s>' expandMacrosWith: error messageText ). - AmqpDisconnectedError signal: error messageText - ] -] - -{ #category : 'private' } -RabbitMQClient >> startProcessing [ - - self subclassResponsibility + self ensureChannelOpen ] { #category : 'controlling' } RabbitMQClient >> stop [ connection - whenConnected: [ connection close ] - whenNot: [ LogRecord emitWarning: 'RabbitMQ connection was already closed.' ] + ifNil: [] + ifNotNil: [ + connection + whenConnected: [connection close] + whenNot: [LogRecord emitWarning: 'RabbitMQ connection was already closed.']] ] -{ #category : 'private - connecting' } -RabbitMQClient >> try: aBlock onConnectivityErrorDo: failBlock [ +{ #category : 'private - accessing' } +RabbitMQClient >> timeframeBetweenAttempts [ - Retry value: aBlock configuredBy: [ :retry | - retry - upTo: self retryCount; - on: self connectivityErrors evaluating: failBlock. - self options at: #retry ifPresent: [ :action | action value: retry ] - ] + ^ Duration milliSeconds: ( options at: #timeSlotBetweenConnectionRetriesInMs ifAbsent: [ 300 ] ) ] { #category : 'private - connecting' } -RabbitMQClient >> withSuccessfulConnectionDo: aBlock [ - - self - try: [ connection whenConnected: [ aBlock value: connection ] whenNot: [ :error | error signal ] ] - onConnectivityErrorDo: [ :attemptNumber :error | - self logFailedConnectionAttempt: attemptNumber dueTo: error. - LogRecord emitWarning: 'Reconnecting socket to RabbitMQ'. - connection initializeSocketConnection - ] +RabbitMQClient >> try: aBlock onConnectivityErrorDo: failBlock [ + + Retry value: aBlock configuredBy: [ :retry | + retry + upTo: self retryCount; + backoffExponentiallyWithTimeSlot: self timeframeBetweenAttempts; + on: self connectivityErrors evaluating: failBlock. + options at: #retry ifPresent: [ :action | action value: retry ] + ] ] diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 2c01d3ca..555834c2 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -8,13 +8,7 @@ as the producers and consumers can operate independently. " Class { #name : 'RabbitMQPublisher', - #superclass : 'Object', - #instVars : [ - 'options', - 'builder', - 'channel', - 'connection' - ], + #superclass : 'RabbitMQClient', #category : 'Ansible-RabbitMQ', #package : 'Ansible-RabbitMQ' } @@ -29,70 +23,10 @@ RabbitMQPublisher class >> configuredBy: aConfigurationAction [ ^ self new initializeConfiguredBy: options ] -{ #category : 'private - accessing' } +{ #category : 'publishing' } RabbitMQPublisher >> channel [ - ^ channel -] - -{ #category : 'private - accessing' } -RabbitMQPublisher >> connectivityErrors [ - - ^ NetworkError , ConnectionClosed , AmqpDisconnectedError -] - -{ #category : 'private - connecting' } -RabbitMQPublisher >> ensureChannelOpen [ - - | reconnect | - - reconnect := [ - self ensureConnectedAndOpen. - channel := connection createChannel - ]. - channel ifNil: reconnect ifNotNil: [ channel whenOpenDo: [ ] whenClosedDo: reconnect ] -] - -{ #category : 'private - connecting' } -RabbitMQPublisher >> ensureConnectedAndOpen [ - - | createConnection | - - createConnection := [ - self - try: [ - connection := builder build. - connection open] - onConnectivityErrorDo: [:attemptNumber :error | - self logFailedConnectionAttempt: attemptNumber dueTo: error]]. - - connection - ifNil: createConnection - ifNotNil: [connection whenConnected: [] whenNot: createConnection]. - - connection - whenOpen: [] - whenNot: [connection closeReason ifNil: [connection open] ifNotNil: createConnection] -] - -{ #category : 'initialization' } -RabbitMQPublisher >> initializeConfiguredBy: anOptionsDictionary [ - - options := anOptionsDictionary. - builder := AmqpConnectionBuilder usingAMQP091Protocol. - builder hostname: (options at: #hostname). - builder portNumber: (options at: #port ifAbsent: [5672]). - builder username: (options at: #username ifAbsent: ['guest']). - builder password: (options at: #password ifAbsent: ['guest']) -] - -{ #category : 'private - logging' } -RabbitMQPublisher >> logFailedConnectionAttempt: anAttemptNumber dueTo: anError [ - - LogRecord emitError: ( 'Attempt #<1p>/<2p> to connect to RabbitMQ failed: <3s>' - expandMacrosWith: anAttemptNumber - with: self retryCount - with: anError messageText ) + ^ channel ] { #category : 'publishing' } @@ -122,49 +56,8 @@ RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ properties: (connection protocolClass basicPropertiesClass new deliveryMode: 2) ] -{ #category : 'private - accessing' } -RabbitMQPublisher >> retryCount [ - - ^options at: #maximumConnectionAttemps ifAbsent: [3] -] - -{ #category : 'private - testing' } -RabbitMQPublisher >> shouldLogDebuggingInfo [ - - ^options at: #enableDebuggingLogs ifAbsent: [false] -] - { #category : 'connecting' } RabbitMQPublisher >> start [ self ensureChannelOpen ] - -{ #category : 'connecting' } -RabbitMQPublisher >> stop [ - - connection - ifNil: [] - ifNotNil: [ - connection - whenConnected: [connection close] - whenNot: [LogRecord emitWarning: 'RabbitMQ connection was already closed.']] -] - -{ #category : 'private - accessing' } -RabbitMQPublisher >> timeframeBetweenAttempts [ - - ^Duration milliSeconds: (options at: #timeSlotBetweenConnectionRetriesInMs ifAbsent: [300]) -] - -{ #category : 'private - connecting' } -RabbitMQPublisher >> try: aBlock onConnectivityErrorDo: failBlock [ - - Retry - value: aBlock - configuredBy: [:retry | - retry - upTo: self retryCount; - backoffExponentiallyWithTimeSlot: self timeframeBetweenAttempts; - on: self connectivityErrors evaluating: failBlock] -] diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 881037af..9a06bb66 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -14,9 +14,6 @@ task during a short HTTP request window. Class { #name : 'RabbitMQWorker', #superclass : 'RabbitMQClient', - #instVars : [ - 'channel' - ], #category : 'Ansible-RabbitMQ', #package : 'Ansible-RabbitMQ' } @@ -29,29 +26,16 @@ RabbitMQWorker class >> isAbstract [ ] { #category : 'private' } -RabbitMQWorker >> configureChannel [ - - channel queueDeclare: self queueName durable: true. - channel prefetchCount: 1. - channel consumeFrom: self queueName applying: [ :message | - self process: message body. - channel basicAck: message method deliveryTag - ] +RabbitMQWorker >> declareQueueInChannel [ + + channel queueDeclare: self queueName durable: true ] { #category : 'private' } RabbitMQWorker >> ensureChannelOpen [ - | reconnect | - - reconnect := [ - self ensureConnectedAndOpen. - channel := connection createChannel - ]. - - channel ifNil: reconnect ifNotNil: [ channel whenOpenDo: [ ] whenClosedDo: reconnect ]. - - self configureChannel + super ensureChannelOpen. + self declareQueueInChannel ] { #category : 'private' } @@ -75,15 +59,37 @@ RabbitMQWorker >> queueName [ ^ self subclassResponsibility ] +{ #category : 'controlling' } +RabbitMQWorker >> start [ + + super start. + self startProcessing +] + { #category : 'private' } -RabbitMQWorker >> startProcessing [ +RabbitMQWorker >> startConsumingFromQueue [ + + self ensureChannelOpen. + + channel prefetchCount: 1. + channel consumeFrom: self queueName applying: [ :message | + self process: message body. + channel basicAck: message method deliveryTag + ] +] - self ensureChannelOpen. +{ #category : 'private' } +RabbitMQWorker >> startProcessing [ - [[ connection waitForEvent ] - on: self connectivityErrors - do: [ :error | - self - logDisconnectionDueTo: error; - ensureChannelOpen]] repeat + self startConsumingFromQueue. + + [ + [ connection waitForEvent ] + on: self connectivityErrors + do: [ :error | + self + logDisconnectionDueTo: error; + startConsumingFromQueue + ] + ] repeat ] From 7895862b42737b3144a5250dba5cd794b78b8ebc Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 12:33:22 -0300 Subject: [PATCH 21/65] handle unexpected errors in heartbeat process --- .../AmqpHeartbeatSender.class.st | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index a82e1adf..d3e94ba1 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -16,6 +16,12 @@ AmqpHeartbeatSender class >> keepingOpen: anAmqpConnection [ ^ self new initializeKeepingOpen: anAmqpConnection ] +{ #category : 'accessing' } +AmqpHeartbeatSender >> connectivityErrors [ + + ^ NetworkError , ConnectionClosed , AmqpDisconnectedError +] + { #category : 'accessing' } AmqpHeartbeatSender >> heartbeatFrame [ @@ -32,36 +38,52 @@ AmqpHeartbeatSender >> initializeKeepingOpen: anAmqpConnection [ isStarted := false ] +{ #category : 'accessing' } +AmqpHeartbeatSender >> mainKeepAliveCycleEvery: aTimeInterval [ + + ^ [ + [ + ( Delay forSeconds: aTimeInterval ) wait. + [ + connection sendFrame: self heartbeatFrame. + connection codec flush + ] + on: self connectivityErrors + do: [ :signal | + LogRecord emitError: + ( 'AMQP Heartbeat failed unexpectedly (<1s>).' expandMacrosWith: signal messageText ). + connection hardCloseDescribedWith: signal messageText. + ^ self + ] + ] repeat + ] ensure: [ self stop ] +] + { #category : 'accessing' } AmqpHeartbeatSender >> spawnProcessSendingHeartbeatEvery: aTimeInterval [ - ^ ( Process - forContext: [ - [ - [ - ( Delay forSeconds: aTimeInterval ) wait. - connection sendFrame: self heartbeatFrame. - connection codec flush - ] repeat - ] ensure: [ process terminate ] - ] asContext - priority: Processor userBackgroundPriority ) - name: 'Heartbeat sender'; - yourself + ^ ( Process + forContext: [ self mainKeepAliveCycleEvery: aTimeInterval ] asContext + priority: Processor userBackgroundPriority ) + name: 'Heartbeat sender'; + yourself ] { #category : 'startup-shutdown' } -AmqpHeartbeatSender >> startBeatingEvery: aTimeInterval [ +AmqpHeartbeatSender >> startBeatingEvery: aTimeInSeconds [ - aTimeInterval ~= 0 - ifTrue: [ process := self spawnProcessSendingHeartbeatEvery: aTimeInterval. - process resume. - isStarted := true - ] + ( aTimeInSeconds strictlyPositive and: [ isStarted not ] ) then: [ + process := self spawnProcessSendingHeartbeatEvery: aTimeInSeconds. + process resume. + isStarted := true + ] ] { #category : 'startup-shutdown' } AmqpHeartbeatSender >> stop [ - process terminate + isStarted then: [ + process ifNotNil: #terminate. + isStarted := false + ] ] From 5f2a04a9f5fd7ffd7064c66fa01706be70a016bd Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 12:36:25 -0300 Subject: [PATCH 22/65] Refactor worker to be used by composition instead of inheritance --- .../RabbitMQTextReverser.class.st | 28 ++++++++------- .../Ansible-RabbitMQ/RabbitMQClient.class.st | 30 +++++++++++----- .../RabbitMQPublisher.class.st | 9 ++--- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 34 ++++++++++++------- 4 files changed, 63 insertions(+), 38 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index c015fab5..ef7a9798 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -1,8 +1,9 @@ Class { #name : 'RabbitMQTextReverser', - #superclass : 'RabbitMQWorker', + #superclass : 'Object', #instVars : [ - 'testCase' + 'testCase', + 'worker' ], #category : 'Ansible-RabbitMQ-Tests', #package : 'Ansible-RabbitMQ-Tests' @@ -17,26 +18,29 @@ RabbitMQTextReverser class >> workingWith: aTestCase [ { #category : 'accessing' } RabbitMQTextReverser >> channel [ - ^ channel + ^ worker channel ] { #category : 'initialization' } RabbitMQTextReverser >> initializeWorkingWith: aTestCase [ - testCase := aTestCase. - self initializeConfiguredBy: ( Dictionary new - at: #hostname put: 'localhost'; - yourself ) + worker := RabbitMQWorker + configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #queueName put: aTestCase queueName + ] + processingMessagesWith: [ :payload | aTestCase storeText: payload utf8Decoded reversed ] ] -{ #category : 'private' } -RabbitMQTextReverser >> process: payload [ +{ #category : 'accessing' } +RabbitMQTextReverser >> start [ - testCase storeText: payload utf8Decoded reversed + ^ worker start ] { #category : 'accessing' } -RabbitMQTextReverser >> queueName [ +RabbitMQTextReverser >> stop [ - ^ testCase queueName + ^ worker stop ] diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 24ba42ed..3670c723 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -9,7 +9,6 @@ Class { #superclass : 'Object', #instVars : [ 'connection', - 'builder', 'channel', 'options' ], @@ -24,12 +23,30 @@ RabbitMQClient class >> isAbstract [ ^ self = RabbitMQClient ] +{ #category : 'publishing' } +RabbitMQClient >> channel [ + + ^ channel +] + { #category : 'private - configuring' } RabbitMQClient >> connectivityErrors [ ^ NetworkError , ConnectionClosed , AmqpDisconnectedError ] +{ #category : 'initialization' } +RabbitMQClient >> createAMQPConnection [ + + | builder | + builder := AmqpConnectionBuilder usingAMQP091Protocol. + builder hostname: ( options at: #hostname ifAbsent: [ 'localhost' ] ). + builder portNumber: ( options at: #port ifAbsent: [ 5672 ] ). + builder username: ( options at: #username ifAbsent: [ 'guest' ] ). + builder password: ( options at: #password ifAbsent: [ 'guest' ] ). + ^ builder build +] + { #category : 'private' } RabbitMQClient >> ensureChannelOpen [ @@ -50,7 +67,7 @@ RabbitMQClient >> ensureConnectedAndOpen [ createConnection := [ self try: [ - connection := builder build. + connection := self createAMQPConnection. connection open] onConnectivityErrorDo: [:attemptNumber :error | self logFailedConnectionAttempt: attemptNumber dueTo: error]]. @@ -65,14 +82,9 @@ RabbitMQClient >> ensureConnectedAndOpen [ ] { #category : 'initialization' } -RabbitMQClient >> initializeConfiguredBy: anOptionsDictionary [ +RabbitMQClient >> initialize [ - options := anOptionsDictionary. - builder := AmqpConnectionBuilder usingAMQP091Protocol. - builder hostname: ( options at: #hostname ). - builder portNumber: ( options at: #port ifAbsent: [ 5672 ] ). - builder username: ( options at: #username ifAbsent: [ 'guest' ] ). - builder password: ( options at: #password ifAbsent: [ 'guest' ] ) + options := Dictionary new ] { #category : 'private - connecting' } diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 555834c2..cc328d6f 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -19,14 +19,15 @@ RabbitMQPublisher class >> configuredBy: aConfigurationAction [ | options | options := Dictionary new. - aConfigurationAction value: options. + aConfigurationAction cull: options. ^ self new initializeConfiguredBy: options ] -{ #category : 'publishing' } -RabbitMQPublisher >> channel [ +{ #category : 'initialization' } +RabbitMQPublisher >> initializeConfiguredBy: anOptionsDictionary [ - ^ channel + super initialize. + options := anOptionsDictionary ] { #category : 'publishing' } diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 9a06bb66..9c74b2a6 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -14,15 +14,23 @@ task during a short HTTP request window. Class { #name : 'RabbitMQWorker', #superclass : 'RabbitMQClient', + #instVars : [ + 'messageProcessor' + ], #category : 'Ansible-RabbitMQ', #package : 'Ansible-RabbitMQ' } { #category : 'testing' } -RabbitMQWorker class >> isAbstract [ - - - ^ self = RabbitMQWorker +RabbitMQWorker class >> configuredBy: aConfigurationAction processingMessagesWith: aMessageProcessor [ + + | options | + options := Dictionary new. + aConfigurationAction cull: options. + options + at: #queueName + ifAbsent: [ InstanceCreationFailed signal: 'Missing name of the queue to consume from' ]. + ^ self new initializeConfiguredBy: options processingMessagesWith: aMessageProcessor ] { #category : 'private' } @@ -39,24 +47,24 @@ RabbitMQWorker >> ensureChannelOpen [ ] { #category : 'private' } -RabbitMQWorker >> logDisconnectionDueTo: anError [ +RabbitMQWorker >> initializeConfiguredBy: anOptionsDictionary processingMessagesWith: aMessageProcessor [ - LogRecord emitError: - ( 'RabbitMQClient disconnected due to <1s>' expandMacrosWith: anError messageText ) + super initialize. + options := anOptionsDictionary. + messageProcessor := aMessageProcessor ] { #category : 'private' } -RabbitMQWorker >> process: payload [ - - "A message was received including the bytes in `payload`" +RabbitMQWorker >> logDisconnectionDueTo: anError [ - self subclassResponsibility + LogRecord emitError: + ( 'RabbitMQClient disconnected due to <1s>' expandMacrosWith: anError messageText ) ] { #category : 'accessing' } RabbitMQWorker >> queueName [ - ^ self subclassResponsibility + ^ options at: #queueName ] { #category : 'controlling' } @@ -73,7 +81,7 @@ RabbitMQWorker >> startConsumingFromQueue [ channel prefetchCount: 1. channel consumeFrom: self queueName applying: [ :message | - self process: message body. + messageProcessor value: message body. channel basicAck: message method deliveryTag ] ] From b7cfac8500e282b2a11adf435ef9b49bd43f6d8d Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 16:23:52 -0300 Subject: [PATCH 23/65] Include Bell-SUnit as dependency to use LoggingAsserter --- source/BaselineOfAnsible/BaselineOfAnsible.class.st | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/BaselineOfAnsible/BaselineOfAnsible.class.st b/source/BaselineOfAnsible/BaselineOfAnsible.class.st index 4cbec742..5c4779a6 100644 --- a/source/BaselineOfAnsible/BaselineOfAnsible.class.st +++ b/source/BaselineOfAnsible/BaselineOfAnsible.class.st @@ -36,6 +36,10 @@ BaselineOfAnsible >> setUpDependencies: spec [ with: [ spec repository: 'github://pharo-contributions/XML-XMLParser:master/src' ]; project: 'XMLParser-Core' copyFrom: 'XMLParser' with: [ spec loads: #( 'Core' ) ]. + spec + baseline: 'Bell' with: [ spec repository: 'github://ba-st/Bell:v3' ]; + project: 'Bell-SUnit' copyFrom: 'Bell' with: [ spec loads: 'Dependent-SUnit-Extensions' ]. + spec baseline: 'Launchpad' with: [ spec repository: 'github://ba-st/Launchpad:v6' ]; project: 'Launchpad-Deployment' copyFrom: 'Launchpad' with: [ spec loads: 'Deployment' ]. @@ -80,7 +84,7 @@ BaselineOfAnsible >> setUpTestPackages: spec [ group: 'Tests' with: 'Ansible-Protocol-Tests'; package: 'Ansible-Protocol-091-Tests' with: [ spec requires: 'Ansible-Protocol-091' ]; group: 'Tests' with: 'Ansible-Protocol-091-Tests'; - package: 'Ansible-RabbitMQ-Tests' with: [ spec requires: #( 'Ansible-RabbitMQ' 'Buoy-SUnit' ) ]; + package: 'Ansible-RabbitMQ-Tests' with: [ spec requires: #( 'Ansible-RabbitMQ' 'Buoy-SUnit' 'Bell-SUnit' ) ]; group: 'Tests' with: 'Ansible-RabbitMQ-Tests' ] From b2f9fa50c1c0061b7463c73aaac834fe22057a00 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 14:21:16 -0300 Subject: [PATCH 24/65] Refactor RabbitMQPublisherTest to use LoggingAsserter Improve AMQPChannel and AMQPConnection printOn --- .../AmqpChannel.class.st | 2 +- .../AmqpConnection.class.st | 24 ++- .../RabbitMQPublisherTest.class.st | 159 +++++++++--------- 3 files changed, 96 insertions(+), 89 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpChannel.class.st b/source/Ansible-Protocol-Core/AmqpChannel.class.st index 176b1a07..beeac052 100644 --- a/source/Ansible-Protocol-Core/AmqpChannel.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannel.class.st @@ -366,7 +366,7 @@ AmqpChannel >> prefetchCount: prefetchCount prefetchSize: prefetchSize global: a { #category : 'AMQP receiving messages' } AmqpChannel >> printOn: aStream [ - aStream nextPutAll: ( 'AMPQ Channel <1p> on connection <2p> (<3s>)' + aStream nextPutAll: ( 'AMPQ Channel <1p> on <2p> (<3s>)' expandMacrosWith: handler channelNumber with: handler connection with: diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index cdeee657..f8dcfa39 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -78,7 +78,7 @@ AmqpConnection >> codec [ { #category : 'accessing' } AmqpConnection >> connectionPairsDescription [ - ^ '<1p>:<2p>-><3s>:<4p>' + ^ '<1s>:<2p>-><3s>:<4p>' expandMacrosWith: socket peerName with: socket port with: hostname @@ -310,7 +310,12 @@ AmqpConnection >> openConnection [ { #category : 'connection-handling' } AmqpConnection >> printOn: aStream [ - aStream nextPutAll: ( 'AMPQ Connection on <1s>' expandMacrosWith: self connectionPairsDescription ) + self + whenConnected: [ + aStream nextPutAll: + ( 'AMPQ Connection on <1s>' expandMacrosWith: self connectionPairsDescription ) ] + whenNot: [ :reason | + aStream nextPutAll: ( 'AMPQ Connection closed due to <1s>' expandMacrosWith: reason ) ] ] { #category : 'connection-handling' } @@ -345,13 +350,14 @@ AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber [ { #category : 'initialization' } AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber ifConnectionClosesWhileWaitingReplyDo: aBlock [ - | ch | - - ch := self ensureChannel: aChannelNumber. - self sendMethod: aRequestMethod onChannel: aChannelNumber. - ^ [ ch waitForReply: aRequestMethod acceptableResponseClasses ] - on: AmqpDisconnectedError , ConnectionClosed - do: [ :signal | aBlock cull: signal ] + ^ [ + | ch | + ch := self ensureChannel: aChannelNumber. + self sendMethod: aRequestMethod onChannel: aChannelNumber. + ch waitForReply: aRequestMethod acceptableResponseClasses + ] + on: AmqpDisconnectedError , ConnectionClosed + do: [ :signal | aBlock cull: signal ] ] { #category : 'connection-handling' } diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st index 0866d40c..b631948b 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st @@ -4,7 +4,8 @@ Class { #instVars : [ 'workerProcess', 'publisher', - 'reversedTexts' + 'reversedTexts', + 'loggingAsserter' ], #category : 'Ansible-RabbitMQ-Tests', #package : 'Ansible-RabbitMQ-Tests' @@ -17,15 +18,15 @@ RabbitMQPublisherTest class >> defaultTimeLimit [ ] { #category : 'private - accessing' } -RabbitMQPublisherTest >> configureConnection: aBuilder [ +RabbitMQPublisherTest >> addTimestampRegexTo: aLogEntryCollection [ - aBuilder hostname: 'localhost'. - aBuilder portNumber: 5672. - aBuilder username: 'guest'. - aBuilder password: 'guest' + ^ aLogEntryCollection collect: [ :regexExpression | + '\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}(\.\d+)?(\+|-)(\d+\:\d+) <1s>' expandMacrosWith: + regexExpression + ] ] -{ #category : 'running' } +{ #category : 'private - accessing' } RabbitMQPublisherTest >> defaultRabbitMQPublisher [ ^ RabbitMQPublisher configuredBy: [ :options | @@ -72,11 +73,24 @@ RabbitMQPublisherTest >> resumeWorkerDuring: aBlock [ self wait ] +{ #category : 'private - support' } +RabbitMQPublisherTest >> runMemoryLoggerDuring: aBlock assertingLogRecordsMatchRegexes: expectedLogEntries [ + + | expectedLogEntriesWithTimestamp | + + expectedLogEntriesWithTimestamp := self addTimestampRegexTo: expectedLogEntries. + + loggingAsserter + runMemoryLoggerDuring: aBlock; + assertLogRecordsMatchUsing: expectedLogEntriesWithTimestamp +] + { #category : 'running' } RabbitMQPublisherTest >> setUp [ super setUp. + loggingAsserter := LoggingAsserter on: self. reversedTexts := OrderedCollection new. workerProcess := @@ -90,8 +104,7 @@ RabbitMQPublisherTest >> setUp [ priority: Processor userBackgroundPriority. publisher := self defaultRabbitMQPublisher. - publisher start. - publisher channel queueDeclare: self queueName durable: true + publisher start ] { #category : 'private - accessing' } @@ -100,7 +113,7 @@ RabbitMQPublisherTest >> storeText: aString [ reversedTexts add: aString ] -{ #category : 'private - accessing' } +{ #category : 'private - support' } RabbitMQPublisherTest >> tearDown [ publisher channel queueDelete: self queueName. @@ -112,94 +125,82 @@ RabbitMQPublisherTest >> tearDown [ { #category : 'running' } RabbitMQPublisherTest >> testCannotStartBecauseNotFoundARabbitMQService [ - | anotherPublisher logger expectedErrorDescription | - - logger := MemoryLogger new. - - anotherPublisher := RabbitMQPublisher configuredBy: [ :options | - options - at: #hostname put: 'looney-tunes.com'; - at: #port put: 1234; - at: #username put: 'bugs'; - at: #password put: 'bunny'; - at: #maximumConnectionAttemps put: 1; - at: #timeSlotBetweenConnectionRetriesInMs put: 1 - ]. - - expectedErrorDescription := 'connection closed while sending data'. - - logger runDuring: [ self shouldFix: [ anotherPublisher start ] ]. - - self assert: ( logger recordings anySatisfy: [ :recording | - recording printString includesSubstring: #'Attempt #1/1 to connect to RabbitMQ failed' ] ) + | anotherPublisher | + anotherPublisher := RabbitMQPublisher configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #port put: 1234; + at: #username put: 'bugs'; + at: #password put: 'bunny'; + at: #maximumConnectionAttemps put: 1; + at: #timeSlotBetweenConnectionRetriesInMs put: 1 + ]. + + self + runMemoryLoggerDuring: [ + self + should: [ anotherPublisher start ] + raise: ConnectionClosed + withMessageText: 'connection closed while sending data' + ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection to localhost\:1234 failed to establish because ConnectionClosed\: Connection aborted to 127.0.0.1\:1234' . + '\[ERROR\] Attempt #1/1 to connect to RabbitMQ failed\: connection closed while sending data' . + '\[INFO\] AMQP connection to localhost\:1234 failed to establish because ConnectionClosed\: Connection aborted to 127.0.0.1\:1234' } ] { #category : 'running' } RabbitMQPublisherTest >> testDebuggingLogsEnabled [ - | anotherPublisher logger | - - logger := MemoryLogger new. - anotherPublisher := self rabbitMQPublisherWithDebuggingLogs. + | anotherPublisher | + anotherPublisher := self rabbitMQPublisherWithDebuggingLogs. - logger runDuring: [ - anotherPublisher start. - anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. - anotherPublisher stop - ]. - - self assert: logger recordings size equals: 3. - - self assert: ( '*[INFO] AMQP connection *->localhost:5672 established successfully' match: - ( logger recordings at: 1 ) printString ). - - self assert: - ( '* [DEBUG] RabbitMQ message published {"messagePublished":"Hola!","routingKey":"tasks-for-testDebuggingLogsEnabled","connectionDescription":"*->localhost:5672"}' - match: ( logger recordings at: 2 ) printString ). - - self assert: ( '* [INFO] AMQP connection *->localhost:5672 closed normally' match: - ( logger recordings at: 3 ) printString ) + self + runMemoryLoggerDuring: [ + anotherPublisher start. + anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. + anotherPublisher stop + ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[DEBUG\] RabbitMQ message published \{"messagePublished"\:"Hola!","routingKey"\:"tasks-for-testDebuggingLogsEnabled","connectionDescription"\:"localhost\:(\d+)->localhost\:5672"\}' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } ] { #category : 'running' } RabbitMQPublisherTest >> testDebuggingLogsTurnedOff [ - | anotherPublisher logger | + | anotherPublisher | - logger := MemoryLogger new. anotherPublisher := self defaultRabbitMQPublisher. - logger runDuring: [ - anotherPublisher start. - anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. - anotherPublisher stop - ]. - - self assert: logger recordings size equals: 2. - - self assert: ( '*[INFO] AMQP connection *->localhost:5672 established successfully' match: - ( logger recordings at: 1 ) printString ). - - self assert: ( '* [INFO] AMQP connection *->localhost:5672 closed normally' match: - ( logger recordings at: 2 ) printString ) + self + runMemoryLoggerDuring: [ + anotherPublisher start. + anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. + anotherPublisher stop + ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } ] { #category : 'running' } RabbitMQPublisherTest >> testPublishingMessageWhenChannelIsTemporallyLost [ - |logger| - - logger := MemoryLogger new. - - self resumeWorkerDuring: [ - logger runDuring: [ publisher channel close. - self publish: #( 'test channel restored' ) onQueueNamed: self queueName ] - - ]. - - self - withTheOnlyOneIn: reversedTexts - do: [ :reversedText | self assert: reversedText equals: 'derotser lennahc tset' ] + self resumeWorkerDuring: [ + self + runMemoryLoggerDuring: [ + publisher start. + publisher channel close. + self publish: #( 'test channel restored' ) onQueueNamed: self queueName + ] + assertingLogRecordsMatchRegexes: { } + ]. + + self + withTheOnlyOneIn: reversedTexts + do: [ :reversedText | self assert: reversedText equals: 'derotser lennahc tset' ] ] { #category : 'running' } From eb45ea84128e3ec22dfc56e7f2bfb6968a735272 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 15:21:04 -0300 Subject: [PATCH 25/65] Refactor RabbitMQTextReverserText to use LoggingAsserter --- .../RabbitMQTextReverserTest.class.st | 110 +++++++++--------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st index f186e534..c145a024 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st @@ -7,12 +7,22 @@ Class { #instVars : [ 'reversedTexts', 'workerProcess', - 'worker' + 'worker', + 'loggingAsserter' ], #category : 'Ansible-RabbitMQ-Tests', #package : 'Ansible-RabbitMQ-Tests' } +{ #category : 'adding' } +RabbitMQTextReverserTest >> addTimestampRegexTo: aLogEntryCollection [ + + ^ aLogEntryCollection collect: [ :regexExpression | + '\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}(\.\d+)?(\+|-)(\d+\:\d+) <1s>' expandMacrosWith: + regexExpression + ] +] + { #category : 'private' } RabbitMQTextReverserTest >> closeAllConnectionsOf: aRabbitmqContainerId for: aUsername because: aCloseReason [ @@ -51,17 +61,10 @@ RabbitMQTextReverserTest >> defaultRabbitMQWorkerUsername [ { #category : 'private' } RabbitMQTextReverserTest >> publish: aText onQueueNamed: aQueueName [ - self withLocalhostConnectionDo: [ :connection | - | channel | + | publisher | + publisher := RabbitMQPublisher configuredBy: [ ]. - channel := connection createChannel. - channel queueDeclare: aQueueName durable: true. - channel - basicPublish: aText utf8Encoded - exchange: '' - routingKey: aQueueName - properties: ( connection protocolClass basicPropertiesClass new deliveryMode: 2 ) - ] + publisher publishOnly: aText onQueueNamed: aQueueName ] { #category : 'accessing' } @@ -86,10 +89,25 @@ RabbitMQTextReverserTest >> resumeWorkerDuring: aBlock [ self wait ] +{ #category : 'private - support' } +RabbitMQTextReverserTest >> runMemoryLoggerDuring: aBlock assertingLogRecordsMatchRegexes: expectedLogEntries [ + + | expectedLogEntriesWithTimestamp | + + expectedLogEntriesWithTimestamp := self addTimestampRegexTo: expectedLogEntries. + + loggingAsserter + runMemoryLoggerDuring: aBlock; + assertLogRecordsMatchUsing: expectedLogEntriesWithTimestamp +] + { #category : 'running' } RabbitMQTextReverserTest >> setUp [ super setUp. + + loggingAsserter := LoggingAsserter on: self. + reversedTexts := OrderedCollection new. worker := RabbitMQTextReverser workingWith: self. @@ -116,42 +134,35 @@ RabbitMQTextReverserTest >> tearDown [ { #category : 'tests' } RabbitMQTextReverserTest >> testProcessingMessageWhenConnectionIsTemporallyLost [ - | logger | + self resumeWorkerDuring: [ + self + publish: 'Hello' onQueueNamed: self queueName; + publish: 'World' onQueueNamed: self queueName. - logger := MemoryLogger new. + self wait. - self resumeWorkerDuring: [ - self - publish: 'Hello' onQueueNamed: self queueName; - publish: 'World' onQueueNamed: self queueName. + self + assert: reversedTexts size equals: 2; + assert: reversedTexts first equals: 'olleH'; + assert: reversedTexts last equals: 'dlroW'. - self wait. + self + runMemoryLoggerDuring: [ + self + closeAllUserConnections; + wait + ] + assertingLogRecordsMatchRegexes: + { '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' }. - self - assert: reversedTexts size equals: 2; - assert: reversedTexts first equals: 'olleH'; - assert: reversedTexts last equals: 'dlroW'. + self publish: 'Test connection restored' onQueueNamed: self queueName + ]. - logger runDuring: [ - self - closeAllUserConnections; - wait - ]. - self publish: 'Test connection restored' onQueueNamed: self queueName - ]. - - self - assert: reversedTexts size equals: 3; - assert: reversedTexts last equals: 'derotser noitcennoc tseT'. - - self assert: logger recordings size equals: 2. - - self assert: ( '*[ERROR] RabbitMQClient disconnected due to Connection closed' match: - ( logger recordings at: 1 ) printString ). - - self assert: ( '* [INFO] AMQP connection *->localhost:5672 established successfully' match: - ( logger recordings at: 2 ) printString ) + self + assert: reversedTexts size equals: 3; + assert: reversedTexts last equals: 'derotser noitcennoc tseT' ] { #category : 'tests' } @@ -182,20 +193,3 @@ RabbitMQTextReverserTest >> wait [ ( Delay forMilliseconds: 200 ) wait ] - -{ #category : 'private' } -RabbitMQTextReverserTest >> withLocalhostConnectionDo: aBlock [ - - | connection | - - connection := AmqpConnectionBuilder usingAMQP091Protocol build. - - connection open. - - connection - whenConnected: [ - aBlock value: connection. - connection close - ] - whenNot: [ :error | self fail: error messageText ] -] From a7a98a8e8236ae1e451d1866e2f3fa984cd13587 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 16:09:05 -0300 Subject: [PATCH 26/65] Add messages ack callback in RabbitMQPublisher --- .../RabbitMQPublisher.class.st | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index cc328d6f..19cf3ba3 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -9,6 +9,9 @@ as the producers and consumers can operate independently. Class { #name : 'RabbitMQPublisher', #superclass : 'RabbitMQClient', + #instVars : [ + 'channelConfigurationCommands' + ], #category : 'Ansible-RabbitMQ', #package : 'Ansible-RabbitMQ' } @@ -23,11 +26,26 @@ RabbitMQPublisher class >> configuredBy: aConfigurationAction [ ^ self new initializeConfiguredBy: options ] +{ #category : 'configuring' } +RabbitMQPublisher >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ + + channelConfigurationCommands add: [ :channel | + channel confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock ] +] + +{ #category : 'initialization' } +RabbitMQPublisher >> ensureChannelOpen [ + + super ensureChannelOpen. + channelConfigurationCommands do: [ :command | command value: channel ] +] + { #category : 'initialization' } RabbitMQPublisher >> initializeConfiguredBy: anOptionsDictionary [ super initialize. - options := anOptionsDictionary + options := anOptionsDictionary. + channelConfigurationCommands := OrderedCollection new ] { #category : 'publishing' } From 208df9546a9975b0e54f23a86d7efea67a73421e Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 16:09:24 -0300 Subject: [PATCH 27/65] Make queue durability configurable in RabbitMQWorker --- source/Ansible-RabbitMQ/RabbitMQWorker.class.st | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 9c74b2a6..6e3de2c3 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -21,6 +21,14 @@ Class { #package : 'Ansible-RabbitMQ' } +{ #category : 'testing' } +RabbitMQWorker class >> configuredBy: aConfigurationAction doingWithPayload: aPayloadProcessor [ + + ^ self + configuredBy: aConfigurationAction + processingMessagesWith: [ :message | aPayloadProcessor value: message body ] +] + { #category : 'testing' } RabbitMQWorker class >> configuredBy: aConfigurationAction processingMessagesWith: aMessageProcessor [ @@ -36,7 +44,7 @@ RabbitMQWorker class >> configuredBy: aConfigurationAction processingMessagesWit { #category : 'private' } RabbitMQWorker >> declareQueueInChannel [ - channel queueDeclare: self queueName durable: true + channel queueDeclare: self queueName durable: ( options at: #queueDurable ifAbsent: [ true ] ) ] { #category : 'private' } @@ -81,7 +89,7 @@ RabbitMQWorker >> startConsumingFromQueue [ channel prefetchCount: 1. channel consumeFrom: self queueName applying: [ :message | - messageProcessor value: message body. + messageProcessor value: message. channel basicAck: message method deliveryTag ] ] From dfbec25ad724567f2b8f0f06e44ab26dbd4d1104 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 16:10:01 -0300 Subject: [PATCH 28/65] Refactor AMQPRabbitTest to user RabbitMQPublisher and Worker --- .../AMQPRabbitTest.class.st | 113 +++++++----------- .../RabbitMQTextReverser.class.st | 2 +- 2 files changed, 46 insertions(+), 69 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st b/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st index 21cd8d80..43abf97f 100644 --- a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st @@ -8,81 +8,58 @@ Class { { #category : 'private' } AMQPRabbitTest >> publish: aMessageCollection onQueueNamed: aQueueName [ - self withLocalhostConnectionDo: [ :connection | - | channel queue confirmationWasReceived | + | publisher confirmationWasReceived queue | + publisher := ( RabbitMQPublisher configuredBy: [ ] ) + confirmMessagesPublicationWith: [ :command | confirmationWasReceived := true ] + andThoseNotProcessedWith: [ :command | self fail ]; + yourself. - confirmationWasReceived := false. + confirmationWasReceived := false. - channel := connection createChannel. - channel queueDeclare: aQueueName. - channel - confirmMessagesPublicationWith: [ :command | confirmationWasReceived := true ] - andThoseNotProcessedWith: [ :command | self fail ]. - aMessageCollection do: [ :message | - channel - basicPublish: message utf8Encoded - exchange: '' - routingKey: aQueueName - properties: ( connection protocolClass basicPropertiesClass new deliveryMode: 2 ) - ]. + publisher ensureChannelOpen. - ( Delay forMilliseconds: 100 ) wait. - queue := channel queueDeclare: aQueueName. + queue := publisher channel queueDeclare: aQueueName. + publisher publish: aMessageCollection onQueueNamed: aQueueName. - self - assert: queue method messageCount equals: aMessageCollection size; - assert: queue method consumerCount equals: 0; - assert: confirmationWasReceived - ] + ( Delay forMilliseconds: 100 ) wait. + queue := publisher channel queueDeclare: aQueueName. + + self + assert: queue method messageCount equals: aMessageCollection size; + assert: queue method consumerCount equals: 0; + assert: confirmationWasReceived ] { #category : 'tests' } AMQPRabbitTest >> testBasicConsumeUsingPublisherConfirmation [ - | channel queue | - - self publish: #('Do it!') onQueueNamed: 'tasks'. - - self - withLocalhostConnectionDo: [ :connection | - channel := connection createChannel. - queue := channel queueDeclare: 'tasks'. - - channel prefetchCount: 1. - - channel - consumeFrom: 'tasks' - applying: [ :messageReceived | - self - assert: messageReceived body asString equals: 'Do it!'; - assert: messageReceived method exchange equals: ''; - assert: messageReceived method routingKey equals: 'tasks'; - deny: messageReceived method redelivered. - - channel basicAck: messageReceived method deliveryTag - ]. - - queue := channel queueDeclare: 'tasks'. - - self - assert: queue method messageCount equals: 0; - assert: queue method consumerCount equals: 1. - - channel queueDelete: 'tasks' - ] -] - -{ #category : 'private' } -AMQPRabbitTest >> withLocalhostConnectionDo: block [ - - | connection | - - connection := AmqpConnectionBuilder usingAMQP091Protocol build. - connection open. - connection - whenConnected: [ - block value: connection. - connection close - ] - whenNot: [ :error | self fail: error messageText ] + | channel queue worker queueName | + queueName := 'tasks'. + + self publish: #( 'Do it!' ) onQueueNamed: queueName. + + worker := RabbitMQWorker + configuredBy: [ :options | + options + at: #queueName put: queueName; + at: #queueDurable put: false + ] + processingMessagesWith: [ :messageReceived | + self + assert: messageReceived body asString equals: 'Do it!'; + assert: messageReceived method exchange equals: ''; + assert: messageReceived method routingKey equals: queueName; + deny: messageReceived method redelivered + ]. + + worker startConsumingFromQueue. + [ + channel := worker channel. + + queue := channel queueDeclare: queueName. + + self + assert: queue method messageCount equals: 0; + assert: queue method consumerCount equals: 1 + ] ensure: [ worker stop ] ] diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index ef7a9798..2d022752 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -30,7 +30,7 @@ RabbitMQTextReverser >> initializeWorkingWith: aTestCase [ at: #hostname put: 'localhost'; at: #queueName put: aTestCase queueName ] - processingMessagesWith: [ :payload | aTestCase storeText: payload utf8Decoded reversed ] + doingWithPayload: [ :payload | aTestCase storeText: payload utf8Decoded reversed ] ] { #category : 'accessing' } From 5df104cae8dcf2e7024eae58cf9db6a228447806 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 16:11:44 -0300 Subject: [PATCH 29/65] Make AmqpConnection printOn more robust to disconnections --- source/Ansible-Protocol-Core/AmqpConnection.class.st | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index f8dcfa39..cd6b81b8 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -78,9 +78,8 @@ AmqpConnection >> codec [ { #category : 'accessing' } AmqpConnection >> connectionPairsDescription [ - ^ '<1s>:<2p>-><3s>:<4p>' - expandMacrosWith: socket peerName - with: socket port + ^ 'localhost:<1p>-><2s>:<3p>' + expandMacrosWith: socket port with: hostname with: portNumber ] From 591df2bf48c64792288aea608bb20e4daa4f248b Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 16:29:37 -0300 Subject: [PATCH 30/65] Upgrade actions to use checkout@v4 --- .github/workflows/loading-groups.yml | 2 +- .github/workflows/markdown-lint.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/loading-groups.yml b/.github/workflows/loading-groups.yml index aef9df16..0751d07c 100644 --- a/.github/workflows/loading-groups.yml +++ b/.github/workflows/loading-groups.yml @@ -18,7 +18,7 @@ jobs: - 5672:5672 options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: ${{ matrix.smalltalk }} diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml index b21e7e07..305d65bc 100644 --- a/.github/workflows/markdown-lint.yml +++ b/.github/workflows/markdown-lint.yml @@ -5,7 +5,7 @@ jobs: name: runner / markdownlint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: markdownlint uses: reviewdog/action-markdownlint@v0 with: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index b076ea00..de4d60db 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -18,7 +18,7 @@ jobs: - 5672:5672 options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: ${{ matrix.smalltalk }} From 7b9aba63eebee117e37a8a8221e760e823fb8dac Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 17:43:05 -0300 Subject: [PATCH 31/65] Make RabbitMQPublisherTest more portable to previous versions --- .../AmqpConnection.class.st | 41 ++++++++----------- .../RabbitMQPublisherTest.class.st | 12 ++---- .../RabbitMQPublisher.class.st | 4 +- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index cd6b81b8..6e6dfc9b 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -166,29 +166,24 @@ AmqpConnection >> initializeHeartbeatSender [ { #category : 'initialization' } AmqpConnection >> initializeSocketConnection [ - socket := Socket newTCP. - [ - socket connectToHostNamed: hostname port: portNumber. - socketConnectionStatus := SuccesfulSocketConnection new - ] - on: NetworkError - do: [ :error | - socketConnectionStatus := FailedSocketConnection dueTo: error. - error return - ]. - - self - whenConnected: [ - LogRecord emitInfo: - ( 'AMQP connection <1s> established successfully' expandMacrosWith: - self connectionPairsDescription ) - ] - whenNot: [ :error | - LogRecord emitInfo: ( 'AMQP connection to <1s>:<2s> failed to establish because <3s>' - expandMacrosWith: hostname - with: portNumber printString - with: error printString ) - ] + socket := Socket newTCP. + [ + socket connectToHostNamed: hostname port: portNumber. + socketConnectionStatus := SuccesfulSocketConnection new. + + LogRecord emitInfo: + ( 'AMQP connection <1s> established successfully' expandMacrosWith: + self connectionPairsDescription ) + ] + on: NetworkError + do: [ :error | + socketConnectionStatus := FailedSocketConnection dueTo: error. + LogRecord emitInfo: ( 'AMQP connection to <1s>:<2s> failed to establish because <3s>' + expandMacrosWith: hostname + with: portNumber printString + with: error printString ). + error pass + ] ] { #category : 'initialization' } diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st index b631948b..b1e165fe 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st @@ -138,15 +138,11 @@ RabbitMQPublisherTest >> testCannotStartBecauseNotFoundARabbitMQService [ self runMemoryLoggerDuring: [ - self - should: [ anotherPublisher start ] - raise: ConnectionClosed - withMessageText: 'connection closed while sending data' - ] + self should: [ anotherPublisher start ] raise: NetworkError ] assertingLogRecordsMatchRegexes: - { '\[INFO\] AMQP connection to localhost\:1234 failed to establish because ConnectionClosed\: Connection aborted to 127.0.0.1\:1234' . - '\[ERROR\] Attempt #1/1 to connect to RabbitMQ failed\: connection closed while sending data' . - '\[INFO\] AMQP connection to localhost\:1234 failed to establish because ConnectionClosed\: Connection aborted to 127.0.0.1\:1234' } + { '\[INFO\] AMQP connection to localhost\:1234 failed to establish because (ConnectionClosed\: Connection aborted|ConnectionTimedOut\: Cannot connect) to 127.0.0.1\:1234' . + '\[ERROR\] Attempt #1/1 to connect to RabbitMQ failed\: (Connection aborted|Cannot connect) to 127.0.0.1\:1234' . + '\[INFO\] AMQP connection to localhost\:1234 failed to establish because (ConnectionClosed\: Connection aborted|ConnectionTimedOut\: Cannot connect) to 127.0.0.1\:1234' } ] { #category : 'running' } diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 19cf3ba3..4958437d 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -29,8 +29,8 @@ RabbitMQPublisher class >> configuredBy: aConfigurationAction [ { #category : 'configuring' } RabbitMQPublisher >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ - channelConfigurationCommands add: [ :channel | - channel confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock ] + channelConfigurationCommands add: [ :theChannel | + theChannel confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock ] ] { #category : 'initialization' } From e90f1dc977e4a5c7c0e5b30557ffa534bc61c926 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 29 Jul 2024 18:49:15 -0300 Subject: [PATCH 32/65] Remove redundant error handled --- source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st | 2 +- source/Ansible-RabbitMQ/RabbitMQClient.class.st | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index d3e94ba1..9d7d858c 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -19,7 +19,7 @@ AmqpHeartbeatSender class >> keepingOpen: anAmqpConnection [ { #category : 'accessing' } AmqpHeartbeatSender >> connectivityErrors [ - ^ NetworkError , ConnectionClosed , AmqpDisconnectedError + ^ NetworkError , AmqpDisconnectedError ] { #category : 'accessing' } diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 3670c723..c9a2db26 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -32,7 +32,7 @@ RabbitMQClient >> channel [ { #category : 'private - configuring' } RabbitMQClient >> connectivityErrors [ - ^ NetworkError , ConnectionClosed , AmqpDisconnectedError + ^ NetworkError , AmqpDisconnectedError ] { #category : 'initialization' } From df1ca68f4861be656fd716e69037c343ec6a5433 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Thu, 1 Aug 2024 17:03:46 -0300 Subject: [PATCH 33/65] :wastebasket: Deprecate method #confirmMessagesPublicationWith: andThoseNotProcessedWith: for: #confirmPublicationWith:otherwise: --- .../RabbitMQPublisher.extension.st | 13 +++++++++++++ source/Ansible-Deprecated-v3/package.st | 1 + source/Ansible-RabbitMQ/AmqpChannel.extension.st | 2 +- source/Ansible-RabbitMQ/RabbitMQPublisher.class.st | 4 ++-- 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st create mode 100644 source/Ansible-Deprecated-v3/package.st diff --git a/source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st b/source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st new file mode 100644 index 00000000..9fff0f71 --- /dev/null +++ b/source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st @@ -0,0 +1,13 @@ +Extension { #name : 'RabbitMQPublisher' } + +{ #category : '*Ansible-Deprecated-v3' } +RabbitMQPublisher >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ + + self + deprecated: 'Use confirmPublicationWith:otherwise:' + transformWith: + '`@receiver confirmMessagesPublicationWith: `@anAckBlock andThoseNotProcessedWith: `@aNackBlock' + -> '`@receiver confirmPublicationWith: `@anAckBlock otherwise: `@aNackBlock'. + + self confirmPublicationWith: anAckBlock otherwise: aNackBlock +] diff --git a/source/Ansible-Deprecated-v3/package.st b/source/Ansible-Deprecated-v3/package.st new file mode 100644 index 00000000..fdf27112 --- /dev/null +++ b/source/Ansible-Deprecated-v3/package.st @@ -0,0 +1 @@ +Package { #name : 'Ansible-Deprecated-v3' } diff --git a/source/Ansible-RabbitMQ/AmqpChannel.extension.st b/source/Ansible-RabbitMQ/AmqpChannel.extension.st index 0b39a552..83252a90 100644 --- a/source/Ansible-RabbitMQ/AmqpChannel.extension.st +++ b/source/Ansible-RabbitMQ/AmqpChannel.extension.st @@ -1,7 +1,7 @@ Extension { #name : 'AmqpChannel' } { #category : '*Ansible-RabbitMQ' } -AmqpChannel >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ +AmqpChannel >> confirmPublicationWith: anAckBlock otherwise: aNackBlock [ self rpc: protocolVersion confirmSelectMethod new. diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 4958437d..61dfb616 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -27,10 +27,10 @@ RabbitMQPublisher class >> configuredBy: aConfigurationAction [ ] { #category : 'configuring' } -RabbitMQPublisher >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ +RabbitMQPublisher >> confirmPublicationWith: anAckBlock otherwise: aNackBlock [ channelConfigurationCommands add: [ :theChannel | - theChannel confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock ] + theChannel confirmPublicationWith: anAckBlock otherwise: aNackBlock ] ] { #category : 'initialization' } From 7c9e7e52db91a5be2bf94035df3021a752df2cd4 Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Thu, 1 Aug 2024 17:09:16 -0300 Subject: [PATCH 34/65] :memo: Add migration guide doc --- docs/MigrationGuide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/MigrationGuide.md diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md new file mode 100644 index 00000000..d8e74e34 --- /dev/null +++ b/docs/MigrationGuide.md @@ -0,0 +1,5 @@ +# Migration Guide + +## Migration from v2 to v3 + +To achieve this, manually load the package `Ansible-Deprecated-V3`. From c087d5ee4a4c256e001007c776d644295b4cc26b Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Thu, 1 Aug 2024 17:14:51 -0300 Subject: [PATCH 35/65] :recycle: Refactor RabbitMQClientTest --- .../AMQPRabbitTest.class.st | 65 ----- .../RabbitMQClientTest.class.st | 250 ++++++++++++++++++ .../RabbitMQPublisherTest.class.st | 228 ---------------- .../RabbitMQTextReverser.class.st | 6 - .../RabbitMQTextReverserTest.class.st | 195 -------------- .../RabbitMQPublisher.class.st | 20 +- 6 files changed, 260 insertions(+), 504 deletions(-) delete mode 100644 source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st create mode 100644 source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st delete mode 100644 source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st delete mode 100644 source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st diff --git a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st b/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st deleted file mode 100644 index 43abf97f..00000000 --- a/source/Ansible-RabbitMQ-Tests/AMQPRabbitTest.class.st +++ /dev/null @@ -1,65 +0,0 @@ -Class { - #name : 'AMQPRabbitTest', - #superclass : 'TestCase', - #category : 'Ansible-RabbitMQ-Tests', - #package : 'Ansible-RabbitMQ-Tests' -} - -{ #category : 'private' } -AMQPRabbitTest >> publish: aMessageCollection onQueueNamed: aQueueName [ - - | publisher confirmationWasReceived queue | - publisher := ( RabbitMQPublisher configuredBy: [ ] ) - confirmMessagesPublicationWith: [ :command | confirmationWasReceived := true ] - andThoseNotProcessedWith: [ :command | self fail ]; - yourself. - - confirmationWasReceived := false. - - publisher ensureChannelOpen. - - queue := publisher channel queueDeclare: aQueueName. - publisher publish: aMessageCollection onQueueNamed: aQueueName. - - ( Delay forMilliseconds: 100 ) wait. - queue := publisher channel queueDeclare: aQueueName. - - self - assert: queue method messageCount equals: aMessageCollection size; - assert: queue method consumerCount equals: 0; - assert: confirmationWasReceived -] - -{ #category : 'tests' } -AMQPRabbitTest >> testBasicConsumeUsingPublisherConfirmation [ - - | channel queue worker queueName | - queueName := 'tasks'. - - self publish: #( 'Do it!' ) onQueueNamed: queueName. - - worker := RabbitMQWorker - configuredBy: [ :options | - options - at: #queueName put: queueName; - at: #queueDurable put: false - ] - processingMessagesWith: [ :messageReceived | - self - assert: messageReceived body asString equals: 'Do it!'; - assert: messageReceived method exchange equals: ''; - assert: messageReceived method routingKey equals: queueName; - deny: messageReceived method redelivered - ]. - - worker startConsumingFromQueue. - [ - channel := worker channel. - - queue := channel queueDeclare: queueName. - - self - assert: queue method messageCount equals: 0; - assert: queue method consumerCount equals: 1 - ] ensure: [ worker stop ] -] diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st new file mode 100644 index 00000000..fa3a2513 --- /dev/null +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -0,0 +1,250 @@ +Class { + #name : 'RabbitMQClientTest', + #superclass : 'TestCase', + #instVars : [ + 'reversedTexts', + 'workerProcess', + 'loggingAsserter', + 'publisher' + ], + #category : 'Ansible-RabbitMQ-Tests', + #package : 'Ansible-RabbitMQ-Tests' +} + +{ #category : 'private - support' } +RabbitMQClientTest >> addTimestampRegexTo: aLogEntryCollection [ + + ^ aLogEntryCollection collect: [ :regexExpression | + '\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}(\.\d+)?(\+|-)(\d+\:\d+) <1s>' expandMacrosWith: + regexExpression + ] +] + +{ #category : 'private - accessing' } +RabbitMQClientTest >> anotherRabbitMQPublisher [ + + ^ RabbitMQPublisher configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #port put: 1234; + at: #username put: 'bugs'; + at: #password put: 'bunny'; + at: #maximumConnectionAttemps put: 1; + at: #timeSlotBetweenConnectionRetriesInMs put: 1 + ] +] + +{ #category : 'private - accessing' } +RabbitMQClientTest >> defaultRabbitMQPublisher [ + + ^ RabbitMQPublisher configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #port put: 5672; + at: #username: put: 'guest'; + at: #password put: 'guest' + ] +] + +{ #category : 'private - accessing' } +RabbitMQClientTest >> queueName [ + + ^ 'tasks-for-' , testSelector +] + +{ #category : 'private - accessing' } +RabbitMQClientTest >> rabbitMQPublisherWithDebuggingLogs [ + + ^ RabbitMQPublisher configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #port put: 5672; + at: #username: put: 'guest'; + at: #password put: 'guest'; + at: #enableDebuggingLogs put: true + ] +] + +{ #category : 'private - support' } +RabbitMQClientTest >> resumeWorkerDuring: aBlock [ + + workerProcess resume. + Processor yield. + self wait. + aBlock value. + self wait +] + +{ #category : 'private - support' } +RabbitMQClientTest >> runMemoryLoggerDuring: aBlock assertingLogRecordsMatchRegexes: expectedLogEntries [ + + | expectedLogEntriesWithTimestamp | + + expectedLogEntriesWithTimestamp := self addTimestampRegexTo: expectedLogEntries. + + loggingAsserter + runMemoryLoggerDuring: aBlock; + assertLogRecordsMatchUsing: expectedLogEntriesWithTimestamp +] + +{ #category : 'running' } +RabbitMQClientTest >> setUp [ + + super setUp. + + loggingAsserter := LoggingAsserter on: self. + reversedTexts := OrderedCollection new. + + workerProcess := + [| worker | + worker := RabbitMQTextReverser workingWith: self. + [worker start] ensure: [worker stop]] + newProcess. + + workerProcess + name: 'Text reverser worker'; + priority: Processor userBackgroundPriority. + + publisher := self defaultRabbitMQPublisher. + publisher start +] + +{ #category : 'private - accessing' } +RabbitMQClientTest >> storeText: aString [ + + reversedTexts add: aString +] + +{ #category : 'running' } +RabbitMQClientTest >> tearDown [ + + publisher channel queueDelete: self queueName. + publisher stop. + workerProcess terminate. + super tearDown +] + +{ #category : 'tests' } +RabbitMQClientTest >> testCannotStartBecauseNotFoundARabbitMQService [ + + self + runMemoryLoggerDuring: [ self should: [ self anotherRabbitMQPublisher start ] raise: NetworkError ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection to localhost\:1234 failed to establish because (ConnectionClosed\: Connection aborted|ConnectionTimedOut\: Cannot connect) to 127.0.0.1\:1234' . + '\[ERROR\] Attempt #1/1 to connect to RabbitMQ failed\: (Connection aborted|Cannot connect) to 127.0.0.1\:1234' . + '\[INFO\] AMQP connection to localhost\:1234 failed to establish because (ConnectionClosed\: Connection aborted|ConnectionTimedOut\: Cannot connect) to 127.0.0.1\:1234' } +] + +{ #category : 'tests' } +RabbitMQClientTest >> testDebuggingLogsEnabled [ + + | anotherPublisher | + + anotherPublisher := self rabbitMQPublisherWithDebuggingLogs. + + self + runMemoryLoggerDuring: [ + anotherPublisher start. + anotherPublisher publishOnly: 'Hello!' onQueueNamed: self queueName. + anotherPublisher stop + ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[DEBUG\] RabbitMQ message published \{"messagePublished"\:"Hello!","routingKey"\:"tasks-for-testDebuggingLogsEnabled","connectionDescription"\:"localhost\:(\d+)->localhost\:5672"\}' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } +] + +{ #category : 'tests' } +RabbitMQClientTest >> testDebuggingLogsTurnedOff [ + + | anotherPublisher | + + anotherPublisher := self defaultRabbitMQPublisher. + + self + runMemoryLoggerDuring: [ + anotherPublisher start. + anotherPublisher publishOnly: 'Hello!' onQueueNamed: self queueName. + anotherPublisher stop + ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } +] + +{ #category : 'tests' } +RabbitMQClientTest >> testPublisherConfirmationWhenMessageProcessed [ + + | confirmationWasReceived queue worker queueName messagesToSend | + + queueName := 'tasks'. + confirmationWasReceived := false. + messagesToSend := #( 'Do it!' ). + + publisher + confirmPublicationWith: [ :command | confirmationWasReceived := true ] + otherwise: [ :command | self fail ]. + + queue := publisher channel queueDeclare: queueName. + + publisher publish: #( 'Do it!' ) onQueueNamed: queueName. + + self wait. + + queue := publisher channel queueDeclare: queueName. + + self + assert: queue method messageCount equals: messagesToSend size; + assert: queue method consumerCount equals: 0; + assert: confirmationWasReceived. + + worker := RabbitMQWorker + configuredBy: [ :options | + options + at: #queueName put: queueName; + at: #queueDurable put: false + ] + processingMessagesWith: [ :messageReceived | + self + assert: messageReceived body asString equals: 'Do it!'; + assert: messageReceived method exchange equals: ''; + assert: messageReceived method routingKey equals: queueName; + deny: messageReceived method redelivered + ]. + + worker startConsumingFromQueue. + [ + queue := worker channel queueDeclare: queueName. + + self + assert: queue method messageCount equals: 0; + assert: queue method consumerCount equals: 1 + ] ensure: [ worker stop ] +] + +{ #category : 'tests' } +RabbitMQClientTest >> testPublishingMessages [ + + self resumeWorkerDuring: [ publisher publish: #( 'Hello' 'World' ) onQueueNamed: self queueName ]. + + self + assert: reversedTexts size equals: 2; + assert: reversedTexts first equals: 'olleH'; + assert: reversedTexts second equals: 'dlroW' +] + +{ #category : 'tests' } +RabbitMQClientTest >> testPublishingOneMessage [ + + self resumeWorkerDuring: [ publisher publishOnly: 'Hello' onQueueNamed: self queueName ]. + + self + withTheOnlyOneIn: reversedTexts + do: [ :reversedText | self assert: reversedText equals: 'olleH' ] +] + +{ #category : 'private - support' } +RabbitMQClientTest >> wait [ + + ( Delay forMilliseconds: 200 ) wait +] diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st deleted file mode 100644 index b1e165fe..00000000 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQPublisherTest.class.st +++ /dev/null @@ -1,228 +0,0 @@ -Class { - #name : 'RabbitMQPublisherTest', - #superclass : 'TestCase', - #instVars : [ - 'workerProcess', - 'publisher', - 'reversedTexts', - 'loggingAsserter' - ], - #category : 'Ansible-RabbitMQ-Tests', - #package : 'Ansible-RabbitMQ-Tests' -} - -{ #category : 'accessing' } -RabbitMQPublisherTest class >> defaultTimeLimit [ - - ^ ( Socket standardTimeout + 60 ) seconds -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> addTimestampRegexTo: aLogEntryCollection [ - - ^ aLogEntryCollection collect: [ :regexExpression | - '\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}(\.\d+)?(\+|-)(\d+\:\d+) <1s>' expandMacrosWith: - regexExpression - ] -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> defaultRabbitMQPublisher [ - - ^ RabbitMQPublisher configuredBy: [ :options | - options - at: #hostname put: 'localhost'; - at: #port put: 5672; - at: #username: put: 'guest'; - at: #password put: 'guest' - ] -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> publish: anObject onQueueNamed: aQueueName [ - - publisher publish: anObject onQueueNamed: aQueueName -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> queueName [ - - ^ 'tasks-for-' , testSelector -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> rabbitMQPublisherWithDebuggingLogs [ - - ^ RabbitMQPublisher configuredBy: [ :options | - options - at: #hostname put: 'localhost'; - at: #port put: 5672; - at: #username: put: 'guest'; - at: #password put: 'guest'; - at: #enableDebuggingLogs put: true - ] -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> resumeWorkerDuring: aBlock [ - - workerProcess resume. - Processor yield. - self wait. - aBlock value. - self wait -] - -{ #category : 'private - support' } -RabbitMQPublisherTest >> runMemoryLoggerDuring: aBlock assertingLogRecordsMatchRegexes: expectedLogEntries [ - - | expectedLogEntriesWithTimestamp | - - expectedLogEntriesWithTimestamp := self addTimestampRegexTo: expectedLogEntries. - - loggingAsserter - runMemoryLoggerDuring: aBlock; - assertLogRecordsMatchUsing: expectedLogEntriesWithTimestamp -] - -{ #category : 'running' } -RabbitMQPublisherTest >> setUp [ - - super setUp. - - loggingAsserter := LoggingAsserter on: self. - reversedTexts := OrderedCollection new. - - workerProcess := - [| worker | - worker := RabbitMQTextReverser workingWith: self. - [worker start] ensure: [worker stop]] - newProcess. - - workerProcess - name: 'Text reverser worker'; - priority: Processor userBackgroundPriority. - - publisher := self defaultRabbitMQPublisher. - publisher start -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> storeText: aString [ - - reversedTexts add: aString -] - -{ #category : 'private - support' } -RabbitMQPublisherTest >> tearDown [ - - publisher channel queueDelete: self queueName. - publisher stop. - workerProcess terminate. - super tearDown -] - -{ #category : 'running' } -RabbitMQPublisherTest >> testCannotStartBecauseNotFoundARabbitMQService [ - - | anotherPublisher | - anotherPublisher := RabbitMQPublisher configuredBy: [ :options | - options - at: #hostname put: 'localhost'; - at: #port put: 1234; - at: #username put: 'bugs'; - at: #password put: 'bunny'; - at: #maximumConnectionAttemps put: 1; - at: #timeSlotBetweenConnectionRetriesInMs put: 1 - ]. - - self - runMemoryLoggerDuring: [ - self should: [ anotherPublisher start ] raise: NetworkError ] - assertingLogRecordsMatchRegexes: - { '\[INFO\] AMQP connection to localhost\:1234 failed to establish because (ConnectionClosed\: Connection aborted|ConnectionTimedOut\: Cannot connect) to 127.0.0.1\:1234' . - '\[ERROR\] Attempt #1/1 to connect to RabbitMQ failed\: (Connection aborted|Cannot connect) to 127.0.0.1\:1234' . - '\[INFO\] AMQP connection to localhost\:1234 failed to establish because (ConnectionClosed\: Connection aborted|ConnectionTimedOut\: Cannot connect) to 127.0.0.1\:1234' } -] - -{ #category : 'running' } -RabbitMQPublisherTest >> testDebuggingLogsEnabled [ - - | anotherPublisher | - anotherPublisher := self rabbitMQPublisherWithDebuggingLogs. - - self - runMemoryLoggerDuring: [ - anotherPublisher start. - anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. - anotherPublisher stop - ] - assertingLogRecordsMatchRegexes: - { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[DEBUG\] RabbitMQ message published \{"messagePublished"\:"Hola!","routingKey"\:"tasks-for-testDebuggingLogsEnabled","connectionDescription"\:"localhost\:(\d+)->localhost\:5672"\}' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } -] - -{ #category : 'running' } -RabbitMQPublisherTest >> testDebuggingLogsTurnedOff [ - - | anotherPublisher | - - anotherPublisher := self defaultRabbitMQPublisher. - - self - runMemoryLoggerDuring: [ - anotherPublisher start. - anotherPublisher publishOnly: 'Hola!' onQueueNamed: self queueName. - anotherPublisher stop - ] - assertingLogRecordsMatchRegexes: - { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } -] - -{ #category : 'running' } -RabbitMQPublisherTest >> testPublishingMessageWhenChannelIsTemporallyLost [ - - self resumeWorkerDuring: [ - self - runMemoryLoggerDuring: [ - publisher start. - publisher channel close. - self publish: #( 'test channel restored' ) onQueueNamed: self queueName - ] - assertingLogRecordsMatchRegexes: { } - ]. - - self - withTheOnlyOneIn: reversedTexts - do: [ :reversedText | self assert: reversedText equals: 'derotser lennahc tset' ] -] - -{ #category : 'running' } -RabbitMQPublisherTest >> testPublishingMessages [ - - self resumeWorkerDuring: [ - self publish: #( 'first message' 'second message' ) onQueueNamed: self queueName ]. - - self - assert: reversedTexts size equals: 2; - assert: reversedTexts first equals: 'egassem tsrif'; - assert: reversedTexts second equals: 'egassem dnoces' -] - -{ #category : 'running' } -RabbitMQPublisherTest >> testPublishingOneMessage [ - - self resumeWorkerDuring: [ publisher publishOnly: 'one message' onQueueNamed: self queueName ]. - - self - withTheOnlyOneIn: reversedTexts - do: [ :reversedText | self assert: reversedText equals: 'egassem eno' ] -] - -{ #category : 'private - accessing' } -RabbitMQPublisherTest >> wait [ - - ( Delay forMilliseconds: 200 ) wait -] diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index 2d022752..9a94fbd6 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -15,12 +15,6 @@ RabbitMQTextReverser class >> workingWith: aTestCase [ ^self new initializeWorkingWith: aTestCase ] -{ #category : 'accessing' } -RabbitMQTextReverser >> channel [ - - ^ worker channel -] - { #category : 'initialization' } RabbitMQTextReverser >> initializeWorkingWith: aTestCase [ diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st deleted file mode 100644 index c145a024..00000000 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverserTest.class.st +++ /dev/null @@ -1,195 +0,0 @@ -" -A RabbitMQTextReverserTest is a test class for testing the behavior of RabbitMQTextReverser -" -Class { - #name : 'RabbitMQTextReverserTest', - #superclass : 'TestCase', - #instVars : [ - 'reversedTexts', - 'workerProcess', - 'worker', - 'loggingAsserter' - ], - #category : 'Ansible-RabbitMQ-Tests', - #package : 'Ansible-RabbitMQ-Tests' -} - -{ #category : 'adding' } -RabbitMQTextReverserTest >> addTimestampRegexTo: aLogEntryCollection [ - - ^ aLogEntryCollection collect: [ :regexExpression | - '\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}(\.\d+)?(\+|-)(\d+\:\d+) <1s>' expandMacrosWith: - regexExpression - ] -] - -{ #category : 'private' } -RabbitMQTextReverserTest >> closeAllConnectionsOf: aRabbitmqContainerId for: aUsername because: aCloseReason [ - - ^ OSPlatform current runCommand: - ( 'docker exec <1s> rabbitmqctl close_all_user_connections <2s> <3s>' - expandMacrosWith: aRabbitmqContainerId - with: aUsername - with: aCloseReason ) -] - -{ #category : 'private' } -RabbitMQTextReverserTest >> closeAllUserConnections [ - - | rabbitmqContainerId closeReason | - - rabbitmqContainerId := self rabbitMQContainerID. - - closeReason := 'CloseConnectionsTest'. - - rabbitmqContainerId isEmpty - then: [ Error signal: 'Could not find a running RabbitMQ container.' ] - otherwise: [ - self - closeAllConnectionsOf: rabbitmqContainerId - for: self defaultRabbitMQWorkerUsername - because: closeReason - ] -] - -{ #category : 'private' } -RabbitMQTextReverserTest >> defaultRabbitMQWorkerUsername [ - - ^ AmqpConnectionBuilder usingAMQP091Protocol credentials username -] - -{ #category : 'private' } -RabbitMQTextReverserTest >> publish: aText onQueueNamed: aQueueName [ - - | publisher | - publisher := RabbitMQPublisher configuredBy: [ ]. - - publisher publishOnly: aText onQueueNamed: aQueueName -] - -{ #category : 'accessing' } -RabbitMQTextReverserTest >> queueName [ - - ^ 'tasks-for-' , testSelector -] - -{ #category : 'private' } -RabbitMQTextReverserTest >> rabbitMQContainerID [ - - ^ ( OSPlatform current resultOfCommand: 'docker ps -q --filter "name=rabbitmq"' ) trim -] - -{ #category : 'private' } -RabbitMQTextReverserTest >> resumeWorkerDuring: aBlock [ - - workerProcess resume. - Processor yield. - self wait. - aBlock value. - self wait -] - -{ #category : 'private - support' } -RabbitMQTextReverserTest >> runMemoryLoggerDuring: aBlock assertingLogRecordsMatchRegexes: expectedLogEntries [ - - | expectedLogEntriesWithTimestamp | - - expectedLogEntriesWithTimestamp := self addTimestampRegexTo: expectedLogEntries. - - loggingAsserter - runMemoryLoggerDuring: aBlock; - assertLogRecordsMatchUsing: expectedLogEntriesWithTimestamp -] - -{ #category : 'running' } -RabbitMQTextReverserTest >> setUp [ - - super setUp. - - loggingAsserter := LoggingAsserter on: self. - - reversedTexts := OrderedCollection new. - - worker := RabbitMQTextReverser workingWith: self. - - workerProcess := [ [ worker start ] ensure: [ worker stop ] ] newProcess. - workerProcess - name: 'Text reverser worker'; - priority: Processor userBackgroundPriority -] - -{ #category : 'accessing' } -RabbitMQTextReverserTest >> storeText: aString [ - - reversedTexts add: aString -] - -{ #category : 'running' } -RabbitMQTextReverserTest >> tearDown [ - - workerProcess terminate. - super tearDown -] - -{ #category : 'tests' } -RabbitMQTextReverserTest >> testProcessingMessageWhenConnectionIsTemporallyLost [ - - self resumeWorkerDuring: [ - self - publish: 'Hello' onQueueNamed: self queueName; - publish: 'World' onQueueNamed: self queueName. - - self wait. - - self - assert: reversedTexts size equals: 2; - assert: reversedTexts first equals: 'olleH'; - assert: reversedTexts last equals: 'dlroW'. - - self - runMemoryLoggerDuring: [ - self - closeAllUserConnections; - wait - ] - assertingLogRecordsMatchRegexes: - { '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' }. - - self publish: 'Test connection restored' onQueueNamed: self queueName - ]. - - - self - assert: reversedTexts size equals: 3; - assert: reversedTexts last equals: 'derotser noitcennoc tseT' -] - -{ #category : 'tests' } -RabbitMQTextReverserTest >> testProcessingMessages [ - - self resumeWorkerDuring: [ - self - publish: 'Hello' onQueueNamed: self queueName; - publish: 'World' onQueueNamed: self queueName - ]. - - self - assert: reversedTexts size equals: 2; - assert: reversedTexts first equals: 'olleH'; - assert: reversedTexts last equals: 'dlroW' -] - -{ #category : 'tests' } -RabbitMQTextReverserTest >> testProcessingOneMessage [ - - self resumeWorkerDuring: [ self publish: 'Hello' onQueueNamed: self queueName ]. - - self withTheOnlyOneIn: reversedTexts do: [ :text | self assert: text equals: 'olleH' ] -] - -{ #category : 'private' } -RabbitMQTextReverserTest >> wait [ - - ( Delay forMilliseconds: 200 ) wait -] diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 61dfb616..13d54727 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -59,20 +59,20 @@ RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ self ensureChannelOpen. - self shouldLogDebuggingInfo then: [ - LogRecord - emitStructuredDebuggingInfo: 'RabbitMQ message published' - with: [:data | - data - at: #messagePublished put: aMessage; - at: #routingKey put: aQueueName; - at: #connectionDescription put: connection connectionPairsDescription]]. - channel basicPublish: aMessage utf8Encoded exchange: '' routingKey: aQueueName - properties: (connection protocolClass basicPropertiesClass new deliveryMode: 2) + properties: ( connection protocolClass basicPropertiesClass new deliveryMode: 2 ). + + self shouldLogDebuggingInfo then: [ + LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message published' with: [ :data | + data + at: #messagePublished put: aMessage; + at: #routingKey put: aQueueName; + at: #connectionDescription put: connection connectionPairsDescription + ] + ] ] { #category : 'connecting' } From 2d512c37d62c913d770f502304c8181e8d1e7d7c Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Mon, 5 Aug 2024 20:56:51 -0300 Subject: [PATCH 36/65] :wrench: Add extra client properties to AmqpConnection --- .../AmqpConnection.class.st | 64 ++++++++++++------- .../AmqpConnectionBuilder.class.st | 13 +++- .../Ansible-RabbitMQ/RabbitMQClient.class.st | 20 ++++-- .../RabbitMQPublisher.class.st | 26 ++++++-- 4 files changed, 84 insertions(+), 39 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 6e6dfc9b..761c6781 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -15,21 +15,28 @@ Class { 'heartbeatSender', 'hostname', 'socketConnectionStatus', - 'portNumber' + 'portNumber', + 'extraClientProperties' ], #category : 'Ansible-Protocol-Core', #package : 'Ansible-Protocol-Core' } { #category : 'instance creation' } -AmqpConnection class >> to: aHostname over: aPort using: aProtocolVersion with: connectionCredentials parameterizedBy: connectionParameters [ +AmqpConnection class >> to: aHostname + over: aPort + using: aProtocolVersion + with: aConnectionCredentialCollection + parameterizedBy: aConnectionParameterCollection + extraProperties: aClientPropertyCollection [ ^ self new initializeTo: aHostname over: aPort using: aProtocolVersion - with: connectionCredentials - parameterizedBy: connectionParameters + with: aConnectionCredentialCollection + parameterizedBy: aConnectionParameterCollection + extraProperties: aClientPropertyCollection ] { #category : 'connection-handling' } @@ -187,15 +194,23 @@ AmqpConnection >> initializeSocketConnection [ ] { #category : 'initialization' } -AmqpConnection >> initializeTo: aHostname over: aPort using: aProtocolVersion with: connectionCredentials parameterizedBy: connectionParameters [ +AmqpConnection >> initializeTo: aHostname + over: aPort + using: aProtocolVersion + with: aConnectionCredentialCollection + parameterizedBy: aConnectionParameterCollection + extraProperties: aClientPropertyCollection [ protocolClass := aProtocolVersion. hostname := aHostname. portNumber := aPort. - credentials := connectionCredentials. - parameters := connectionParameters. - self initializeSocketConnection. - self initializeHeartbeatSender + credentials := aConnectionCredentialCollection. + parameters := aConnectionParameterCollection. + extraClientProperties := aClientPropertyCollection. + + self + initializeSocketConnection; + initializeHeartbeatSender ] { #category : 'private-opening' } @@ -439,20 +454,23 @@ AmqpConnection >> socketConnectionStatus [ AmqpConnection >> startConnection [ self withNextFrameDo: [ :nextFrame | - | response | - - response := credentials responseFor: nextFrame method. - response ifNil: [ - AmqpDisconnectedError signal: 'No acceptable SASL mechanism for the given credentials' ]. - self - sendMethod: ( self protocolClass connectionStartOkMethod new - clientProperties: ( Dictionary new - at: 'product' put: 'RabbitMQ Smalltalk'; - yourself ); - mechanism: response key; - response: response value ) - onChannel: 0. - credentials := nil + | response clientProperties | + + response := credentials responseFor: nextFrame method. + response ifNil: [ + AmqpDisconnectedError signal: 'No acceptable SASL mechanism for the given credentials' ]. + clientProperties := Dictionary new + at: 'product' put: 'RabbitMQ Smalltalk'; + yourself. + extraClientProperties keysAndValuesDo: [ :key :value | clientProperties at: key put: value ]. + + self + sendMethod: ( self protocolClass connectionStartOkMethod new + clientProperties: clientProperties; + mechanism: response key; + response: response value ) + onChannel: 0. + credentials := nil ] ] diff --git a/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st b/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st index 8cbf420e..86878a49 100644 --- a/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnectionBuilder.class.st @@ -7,7 +7,8 @@ Class { 'username', 'password', 'portNumber', - 'protocolClass' + 'protocolClass', + 'clientProperties' ], #category : 'Ansible-Protocol-Core', #package : 'Ansible-Protocol-Core' @@ -19,10 +20,16 @@ AmqpConnectionBuilder class >> forProtocol: aProtocolClass [ ^self new initializeForProtocol: aProtocolClass ] +{ #category : 'initialization' } +AmqpConnectionBuilder >> atClientProperty: aName put: aValue [ + + clientProperties at: aName put: aValue +] + { #category : 'building' } AmqpConnectionBuilder >> build [ - protocolClass ifNil: [ Error signal: 'Protocol must be configured' ]. + protocolClass ifNil: [ Error signal: 'Protocol must be configured' ]. ^ AmqpConnection to: hostname @@ -30,6 +37,7 @@ AmqpConnectionBuilder >> build [ using: protocolClass with: self credentials parameterizedBy: parameters + extraProperties: clientProperties ] { #category : 'private-accessing' } @@ -60,6 +68,7 @@ AmqpConnectionBuilder >> initialize [ username: 'guest'; password: 'guest'; hostname: 'localhost'. + clientProperties := Dictionary new. parameters := AmqpConnectionParameters new channelMax: 0; frameMax: 131072; diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index c9a2db26..3563cafb 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -38,13 +38,19 @@ RabbitMQClient >> connectivityErrors [ { #category : 'initialization' } RabbitMQClient >> createAMQPConnection [ - | builder | - builder := AmqpConnectionBuilder usingAMQP091Protocol. - builder hostname: ( options at: #hostname ifAbsent: [ 'localhost' ] ). - builder portNumber: ( options at: #port ifAbsent: [ 5672 ] ). - builder username: ( options at: #username ifAbsent: [ 'guest' ] ). - builder password: ( options at: #password ifAbsent: [ 'guest' ] ). - ^ builder build + | builder | + + builder := AmqpConnectionBuilder usingAMQP091Protocol. + builder hostname: ( options at: #hostname ifAbsent: [ 'localhost' ] ). + builder portNumber: ( options at: #port ifAbsent: [ 5672 ] ). + builder username: ( options at: #username ifAbsent: [ 'guest' ] ). + builder password: ( options at: #password ifAbsent: [ 'guest' ] ). + options at: 'extraClientProperties' ifPresent: [ :extraProperties | + extraProperties keysAndValuesDo: [ :propertyName :propertyValue | + builder atClientProperty: propertyName put: propertyValue ] + ]. + + ^ builder build ] { #category : 'private' } diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 13d54727..b2f1a3d2 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -57,13 +57,17 @@ RabbitMQPublisher >> publish: aMessageCollection onQueueNamed: aQueueName [ { #category : 'publishing' } RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ - self ensureChannelOpen. + | tryToPublishMessage | - channel - basicPublish: aMessage utf8Encoded - exchange: '' - routingKey: aQueueName - properties: ( connection protocolClass basicPropertiesClass new deliveryMode: 2 ). + self ensureChannelOpen. + tryToPublishMessage := [ + channel + basicPublish: aMessage utf8Encoded + exchange: '' + routingKey: aQueueName + properties: + ( connection protocolClass basicPropertiesClass new deliveryMode: 2 ) + ]. self shouldLogDebuggingInfo then: [ LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message published' with: [ :data | @@ -72,7 +76,15 @@ RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ at: #routingKey put: aQueueName; at: #connectionDescription put: connection connectionPairsDescription ] - ] + ]. + + tryToPublishMessage + on: self connectivityErrors + do: [ :signal | + connection hardCloseDescribedWith: signal messageText. + self ensureChannelOpen. + signal return: tryToPublishMessage value + ] ] { #category : 'connecting' } From 49b615a7ae513495633a8c93281223d0ff5647aa Mon Sep 17 00:00:00 2001 From: Agustin Salvidio Date: Mon, 5 Aug 2024 20:58:53 -0300 Subject: [PATCH 37/65] :white_check_mark: Add RabbitMQ reconnection test --- .../RabbitMQClientTest.class.st | 101 +++++++++++++++++- .../RabbitMQTextReverser.class.st | 17 +-- 2 files changed, 109 insertions(+), 9 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index fa3a2513..f672edeb 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -11,6 +11,12 @@ Class { #package : 'Ansible-RabbitMQ-Tests' } +{ #category : 'accessing' } +RabbitMQClientTest class >> defaultTimeLimit [ + + ^ ( Socket standardTimeout + 120 ) seconds +] + { #category : 'private - support' } RabbitMQClientTest >> addTimestampRegexTo: aLogEntryCollection [ @@ -34,6 +40,35 @@ RabbitMQClientTest >> anotherRabbitMQPublisher [ ] ] +{ #category : 'private - support' } +RabbitMQClientTest >> closeAllConnectionsOf: aRabbitmqContainerId for: aUsername because: aCloseReason [ + + ^ OSPlatform current runCommand: + ( 'docker exec <1s> rabbitmqctl close_all_user_connections <2s> <3s>' + expandMacrosWith: aRabbitmqContainerId + with: aUsername + with: aCloseReason ) +] + +{ #category : 'private - support' } +RabbitMQClientTest >> closeAllUserConnections [ + + | rabbitmqContainerId closeReason | + + rabbitmqContainerId := self rabbitMQContainerID. + + closeReason := 'CloseConnectionsTest'. + + rabbitmqContainerId isEmpty + then: [ Error signal: 'Could not find a running RabbitMQ container.' ] + otherwise: [ + self + closeAllConnectionsOf: rabbitmqContainerId + for: self defaultRabbitMQWorkerUsername + because: closeReason + ] +] + { #category : 'private - accessing' } RabbitMQClientTest >> defaultRabbitMQPublisher [ @@ -42,16 +77,39 @@ RabbitMQClientTest >> defaultRabbitMQPublisher [ at: #hostname put: 'localhost'; at: #port put: 5672; at: #username: put: 'guest'; - at: #password put: 'guest' + at: #password put: 'guest'; + at: #extraClientProperties put: ( Dictionary new + at: 'process' put: 'RabbitMQClientTest Publisher'; + yourself ) ] ] +{ #category : 'private - accessing' } +RabbitMQClientTest >> defaultRabbitMQWorkerUsername [ + + ^ AmqpConnectionBuilder usingAMQP091Protocol credentials username +] + +{ #category : 'private - support' } +RabbitMQClientTest >> largerWait [ + + "This delay aims to replicate the time required to successfully close all the RabbitMQ connections." + + ( Delay forSeconds: 120 ) wait +] + { #category : 'private - accessing' } RabbitMQClientTest >> queueName [ ^ 'tasks-for-' , testSelector ] +{ #category : 'private - accessing' } +RabbitMQClientTest >> rabbitMQContainerID [ + + ^ ( OSPlatform current resultOfCommand: 'docker ps -q --filter "name=rabbitmq"' ) trim +] + { #category : 'private - accessing' } RabbitMQClientTest >> rabbitMQPublisherWithDebuggingLogs [ @@ -118,7 +176,9 @@ RabbitMQClientTest >> storeText: aString [ { #category : 'running' } RabbitMQClientTest >> tearDown [ - publisher channel queueDelete: self queueName. + [ publisher channel queueDelete: self queueName ] + on: AmqpDisconnectedError + do: [ :signal | " Handle when tests fail and the channel was never opened" signal return ]. publisher stop. workerProcess terminate. super tearDown @@ -222,6 +282,43 @@ RabbitMQClientTest >> testPublisherConfirmationWhenMessageProcessed [ ] ensure: [ worker stop ] ] +{ #category : 'tests' } +RabbitMQClientTest >> testPublishingMessageWhenConnectionIsTemporallyLost [ + + self resumeWorkerDuring: [ + publisher + publishOnly: 'Hello' onQueueNamed: self queueName; + publishOnly: 'World' onQueueNamed: self queueName. + + self wait. + + self + assert: reversedTexts size equals: 2; + assert: reversedTexts first equals: 'olleH'; + assert: reversedTexts last equals: 'dlroW'. + + self + runMemoryLoggerDuring: [ + self + closeAllUserConnections; + largerWait. + publisher publishOnly: 'Test connection restored' onQueueNamed: self queueName + ] + assertingLogRecordsMatchRegexes: + { '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[ERROR\] AMQP Heartbeat failed unexpectedly \(connection closed while sending data\)\.' . + '\[WARNING\] AMQP connection localhost\:(\d+)->localhost\:5672 hard closed due to connection closed while sending data' . + '\[WARNING\] AMQP connection localhost\:(\d+)->localhost\:5672 hard closed due to connection closed while sending data' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } + ]. + + + self + assert: reversedTexts size equals: 3; + assert: reversedTexts last equals: 'derotser noitcennoc tseT' +] + { #category : 'tests' } RabbitMQClientTest >> testPublishingMessages [ diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index 9a94fbd6..20d47ae6 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -18,13 +18,16 @@ RabbitMQTextReverser class >> workingWith: aTestCase [ { #category : 'initialization' } RabbitMQTextReverser >> initializeWorkingWith: aTestCase [ - worker := RabbitMQWorker - configuredBy: [ :options | - options - at: #hostname put: 'localhost'; - at: #queueName put: aTestCase queueName - ] - doingWithPayload: [ :payload | aTestCase storeText: payload utf8Decoded reversed ] + worker := RabbitMQWorker + configuredBy: [ :options | + options + at: #hostname put: 'localhost'; + at: #queueName put: aTestCase queueName; + at: #extraClientProperties put: ( Dictionary new + at: 'process' put: 'Text Reverser Worker'; + yourself ) + ] + doingWithPayload: [ :payload | aTestCase storeText: payload utf8Decoded reversed ] ] { #category : 'accessing' } From 2f859ef01de4c38c1a77f2c4fdefcba06839606b Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Fri, 9 Aug 2024 20:21:30 -0300 Subject: [PATCH 38/65] Extract asserts in testPublisherConfirmationWhenMessageProcessed --- .../RabbitMQClientTest.class.st | 102 ++++++++++-------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index f672edeb..fdf544ad 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -40,6 +40,59 @@ RabbitMQClientTest >> anotherRabbitMQPublisher [ ] ] +{ #category : 'tests' } +RabbitMQClientTest >> assertQueueStatusAfterConsuming: messagesToSend from: queueName [ + + | worker queue messageWasConsumed | + messageWasConsumed := false. + worker := RabbitMQWorker + configuredBy: [ :options | + options + at: #queueName put: queueName; + at: #queueDurable put: false + ] + processingMessagesWith: [ :messageReceived | + messageWasConsumed := true. + self + assert: messageReceived body asString equals: messagesToSend; + assert: messageReceived method exchange equals: ''; + assert: messageReceived method routingKey equals: queueName; + deny: messageReceived method redelivered + ]. + worker startConsumingFromQueue. + [ + queue := worker channel queueDeclare: queueName. + self + assert: messageWasConsumed; + assert: queue method messageCount equals: 0; + assert: queue method consumerCount equals: 1 + ] ensure: [ worker stop ] +] + +{ #category : 'tests' } +RabbitMQClientTest >> assertQueueStatusAfterPublishing: messagesToSend on: queueName [ + + | queue messageWasPublished | + messageWasPublished := false. + + publisher + confirmPublicationWith: [ :command | messageWasPublished := true ] + otherwise: [ :command | self fail ]. + + queue := publisher channel queueDeclare: queueName. + + publisher publishOnly: messagesToSend onQueueNamed: queueName. + + self wait. + + queue := publisher channel queueDeclare: queueName. + + self + assert: queue method messageCount equals: 1; + assert: queue method consumerCount equals: 0; + assert: messageWasPublished +] + { #category : 'private - support' } RabbitMQClientTest >> closeAllConnectionsOf: aRabbitmqContainerId for: aUsername because: aCloseReason [ @@ -235,51 +288,14 @@ RabbitMQClientTest >> testDebuggingLogsTurnedOff [ { #category : 'tests' } RabbitMQClientTest >> testPublisherConfirmationWhenMessageProcessed [ - | confirmationWasReceived queue worker queueName messagesToSend | - - queueName := 'tasks'. - confirmationWasReceived := false. - messagesToSend := #( 'Do it!' ). - - publisher - confirmPublicationWith: [ :command | confirmationWasReceived := true ] - otherwise: [ :command | self fail ]. + | queueName messagesToSend | - queue := publisher channel queueDeclare: queueName. + queueName := self queueName. + messagesToSend := 'Do it!'. - publisher publish: #( 'Do it!' ) onQueueNamed: queueName. - - self wait. - - queue := publisher channel queueDeclare: queueName. - - self - assert: queue method messageCount equals: messagesToSend size; - assert: queue method consumerCount equals: 0; - assert: confirmationWasReceived. - - worker := RabbitMQWorker - configuredBy: [ :options | - options - at: #queueName put: queueName; - at: #queueDurable put: false - ] - processingMessagesWith: [ :messageReceived | - self - assert: messageReceived body asString equals: 'Do it!'; - assert: messageReceived method exchange equals: ''; - assert: messageReceived method routingKey equals: queueName; - deny: messageReceived method redelivered - ]. - - worker startConsumingFromQueue. - [ - queue := worker channel queueDeclare: queueName. - - self - assert: queue method messageCount equals: 0; - assert: queue method consumerCount equals: 1 - ] ensure: [ worker stop ] + self + assertQueueStatusAfterPublishing: messagesToSend on: queueName; + assertQueueStatusAfterConsuming: messagesToSend from: queueName ] { #category : 'tests' } From 38d3ee05afb40862ac77caf8bdc4da64e7244656 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Sat, 10 Aug 2024 01:20:55 -0300 Subject: [PATCH 39/65] Homogeneize AmqpConnection closing logic --- .../AmqpChannel.class.st | 16 --- .../AmqpConnection.class.st | 113 ++++++++---------- .../RabbitMQClientTest.class.st | 14 +-- .../Ansible-RabbitMQ/RabbitMQClient.class.st | 2 +- 4 files changed, 55 insertions(+), 90 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpChannel.class.st b/source/Ansible-Protocol-Core/AmqpChannel.class.st index beeac052..029ecf61 100644 --- a/source/Ansible-Protocol-Core/AmqpChannel.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannel.class.st @@ -311,22 +311,6 @@ AmqpChannel >> handleChannelClose: cmd [ onChannel: handler channelNumber "TODO: notify callbacks of closure" ] -{ #category : 'AMQP closing' } -AmqpChannel >> hardCloseDescribedWith: aDescription [ - - | c | - - handler closeReason - ifNil: [ c := protocolVersion channelCloseMethod new - replyCode: protocolVersion internalError; - replyText: 'Abnormal disconnection'; - classId: 0; - methodId: 0. - handler rpc: c. - handler internalClose: c - ] -] - { #category : 'initialization' } AmqpChannel >> initializeUsing: aChannelHandler [ diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 761c6781..c9b828ab 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -8,7 +8,6 @@ Class { 'parameters', 'virtualHost', 'isOpen', - 'closeReason', 'channels', 'nextChannel', 'protocolClass', @@ -40,40 +39,46 @@ AmqpConnection class >> to: aHostname ] { #category : 'connection-handling' } -AmqpConnection >> close [ - - | connectionClose | - - isOpen ifTrue: [ - isOpen := false. - connectionClose := self protocolClass connectionCloseMethod new - replyCode: self protocolClass replySuccess; - replyText: 'Normal shutdown'; - classId: 0; - methodId: 0. - self - rpc: connectionClose - onChannel: 0 - ifConnectionClosesWhileWaitingReplyDo: [ :signal | "https://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.close-ok A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error. - jvanecek" - LogRecord emitWarning: - ( 'AMQP connection <1s> closed while waiting for the close-ok reply' expandMacrosWith: - self connectionPairsDescription ). - signal return - ]. - self internalClose: connectionClose - ]. - - LogRecord emitInfo: - ( 'AMQP connection <1s> closed normally' expandMacrosWith: self connectionPairsDescription ). - heartbeatSender stop. - socket close. - socketConnectionStatus := HealthyClosedSocketConnection dueTo: 'Normal shutdown' +AmqpConnection >> becomeCloseAfter: aBlock [ + + | closingMethod | + isOpen ifTrue: [ + [ + closingMethod := aBlock value. + channels valuesDo: [ :ch | ch internalClose: closingMethod ]. + ] ensure: [ + LogRecord emitInfo: ( 'AMQP connection <1s> closed due to <2s>' + expandMacrosWith: self connectionPairsDescription + with: closingMethod replyText ). + heartbeatSender stop. + socket close. + isOpen := false. + socketConnectionStatus := HealthyClosedSocketConnection dueTo: closingMethod replyText + ] + ] ] { #category : 'connection-handling' } -AmqpConnection >> closeReason [ +AmqpConnection >> close [ - ^ closeReason + self becomeCloseAfter: [ + | connectionClose | + connectionClose := self protocolClass connectionCloseMethod new + replyCode: self protocolClass replySuccess; + replyText: 'Normal shutdown'; + classId: 0; + methodId: 0. + self + rpc: connectionClose + onChannel: 0 + ifConnectionClosesWhileWaitingReplyDo: [ :signal | "https://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.close-ok A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error. - jvanecek" + LogRecord emitWarning: + ( 'AMQP connection <1s> closed while waiting for the close-ok reply' + expandMacrosWith: self connectionPairsDescription ). + signal return + ]. + connectionClose + ] ] { #category : 'accessing' } @@ -117,40 +122,27 @@ AmqpConnection >> ensureChannel: channelNumber [ { #category : 'connection-handling' } AmqpConnection >> ensureOpen [ - closeReason notNil - ifTrue: [ AmqpDisconnectedError signal: 'Connection closed' ] + ( socket isConnected and: [ socket isOtherEndClosed not ] ) ifFalse: [ + AmqpDisconnectedError signal: 'Connection closed' ] ] { #category : 'connection-handling' } AmqpConnection >> handleConnectionClose: cmd [ - self internalClose: cmd method. - isOpen := false. - self sendMethod: self protocolClass connectionCloseOkMethod new onChannel: 0. - socket close + self becomeCloseAfter: [ + self sendMethod: self protocolClass connectionCloseOkMethod new onChannel: 0. + cmd method + ] ] { #category : 'connection-handling' } AmqpConnection >> hardCloseDescribedWith: aDescription [ - | connectionClose | - - isOpen - ifTrue: [ - isOpen := false. - connectionClose := - (self protocolClass connectionCloseMethod new) - replyCode: self protocolClass internalError; - replyText: 'Abnormal disconnection'. - self internalClose: connectionClose]. - - LogRecord emitWarning: ( - 'AMQP connection <1s> hard closed due to <2s>' - expandMacrosWith: self connectionPairsDescription with: aDescription). - - heartbeatSender stop. - socket close. - socketConnectionStatus := FailedSocketConnection dueTo: aDescription + self becomeCloseAfter: [ + self protocolClass connectionCloseMethod new + replyCode: self protocolClass internalError; + replyText: aDescription + ] ] { #category : 'initialization' } @@ -159,7 +151,6 @@ AmqpConnection >> initialize [ super initialize. virtualHost := '/'. isOpen := false. - closeReason := nil. channels := Dictionary new. nextChannel := 0 ] @@ -222,16 +213,6 @@ AmqpConnection >> installChannel0 [ channel mapEvent: self protocolClass connectionCloseMethod to: self selector: #handleConnectionClose: ] -{ #category : 'connection-handling' } -AmqpConnection >> internalClose: method [ - - closeReason - ifNil: [ closeReason := method. - channels values copy do: [ :ch | ch internalClose: method ]. - self changed: #connectionClosed - ] -] - { #category : 'connection-handling' } AmqpConnection >> mainLoopCycle [ diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index fdf544ad..443e15d3 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -230,7 +230,7 @@ RabbitMQClientTest >> storeText: aString [ RabbitMQClientTest >> tearDown [ [ publisher channel queueDelete: self queueName ] - on: AmqpDisconnectedError + on: AmqpDisconnectedError, NetworkError do: [ :signal | " Handle when tests fail and the channel was never opened" signal return ]. publisher stop. workerProcess terminate. @@ -264,7 +264,7 @@ RabbitMQClientTest >> testDebuggingLogsEnabled [ assertingLogRecordsMatchRegexes: { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . '\[DEBUG\] RabbitMQ message published \{"messagePublished"\:"Hello!","routingKey"\:"tasks-for-testDebuggingLogsEnabled","connectionDescription"\:"localhost\:(\d+)->localhost\:5672"\}' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to Normal shutdown' } ] { #category : 'tests' } @@ -282,7 +282,7 @@ RabbitMQClientTest >> testDebuggingLogsTurnedOff [ ] assertingLogRecordsMatchRegexes: { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed normally' } + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to Normal shutdown' } ] { #category : 'tests' } @@ -321,11 +321,11 @@ RabbitMQClientTest >> testPublishingMessageWhenConnectionIsTemporallyLost [ publisher publishOnly: 'Test connection restored' onQueueNamed: self queueName ] assertingLogRecordsMatchRegexes: - { '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . + { + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to CONNECTION_FORCED - CloseConnectionsTest' . + '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[ERROR\] AMQP Heartbeat failed unexpectedly \(connection closed while sending data\)\.' . - '\[WARNING\] AMQP connection localhost\:(\d+)->localhost\:5672 hard closed due to connection closed while sending data' . - '\[WARNING\] AMQP connection localhost\:(\d+)->localhost\:5672 hard closed due to connection closed while sending data' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to connection closed while sending data' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } ]. diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 3563cafb..77796cb3 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -84,7 +84,7 @@ RabbitMQClient >> ensureConnectedAndOpen [ connection whenOpen: [] - whenNot: [connection closeReason ifNil: [connection open] ifNotNil: createConnection] + whenNot: [connection open] ] { #category : 'initialization' } From 504e44068542978cadb9f80d76fb54ac3c0544a4 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Sat, 10 Aug 2024 01:22:17 -0300 Subject: [PATCH 40/65] Increment heartbeat priority to highIO Also improve printOn --- source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index 9d7d858c..a041d846 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -64,8 +64,8 @@ AmqpHeartbeatSender >> spawnProcessSendingHeartbeatEvery: aTimeInterval [ ^ ( Process forContext: [ self mainKeepAliveCycleEvery: aTimeInterval ] asContext - priority: Processor userBackgroundPriority ) - name: 'Heartbeat sender'; + priority: Processor highIOPriority ) + name: ( 'Heartbeat on <1p>' expandMacrosWith: connection ); yourself ] From 62b8c7db4c112080f37635571dba6f22b0ff679c Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Sat, 10 Aug 2024 03:07:36 -0300 Subject: [PATCH 41/65] Use ZdcSocketStream because it's flush handles better the disconnections across pharo versions SocketStream>>#flush don't detect them properly in Pharo 11 and previous versions --- .../AmqpConnection.class.st | 21 ++++++++----------- .../RabbitMQClientTest.class.st | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index c9b828ab..c4312edc 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -268,20 +268,17 @@ AmqpConnection >> nextFrame [ { #category : 'opening' } AmqpConnection >> open [ - codec := AmqpCodec - on: - ((SocketStream on: socket) - noTimeout; - binary; - yourself). + codec := AmqpCodec on: ( ( ZdcSocketStream on: socket ) + binary; + yourself ). - self - sendProtocolHeader; - startConnection; - tuneConnection; - openConnection. + self + sendProtocolHeader; + startConnection; + tuneConnection; + openConnection. - heartbeatSender startBeatingEvery: parameters heartbeat + heartbeatSender startBeatingEvery: parameters heartbeat ] { #category : 'private-opening' } diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index 443e15d3..2e83841e 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -325,7 +325,7 @@ RabbitMQClientTest >> testPublishingMessageWhenConnectionIsTemporallyLost [ '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to CONNECTION_FORCED - CloseConnectionsTest' . '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to connection closed while sending data' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to (connection closed while sending data|Data send timed out.)' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } ]. From eca1148ffb219bef74094735abd5846d18bcaa23 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Sat, 10 Aug 2024 03:36:28 -0300 Subject: [PATCH 42/65] Create an AmqpSocketStream with a #flush implementation consistent for every pharo version --- .../AmqpConnection.class.st | 3 ++- .../AmqpSocketStream.class.st | 22 +++++++++++++++++++ .../RabbitMQClientTest.class.st | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 source/Ansible-Protocol-Core/AmqpSocketStream.class.st diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index c4312edc..29899a54 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -268,7 +268,8 @@ AmqpConnection >> nextFrame [ { #category : 'opening' } AmqpConnection >> open [ - codec := AmqpCodec on: ( ( ZdcSocketStream on: socket ) + codec := AmqpCodec on: ( ( AmqpSocketStream on: socket ) + noTimeout; binary; yourself ). diff --git a/source/Ansible-Protocol-Core/AmqpSocketStream.class.st b/source/Ansible-Protocol-Core/AmqpSocketStream.class.st new file mode 100644 index 00000000..14819b8c --- /dev/null +++ b/source/Ansible-Protocol-Core/AmqpSocketStream.class.st @@ -0,0 +1,22 @@ +Class { + #name : 'AmqpSocketStream', + #superclass : 'SocketStream', + #category : 'Ansible-Protocol-Core', + #package : 'Ansible-Protocol-Core' +} + +{ #category : 'control' } +AmqpSocketStream >> flush [ + "If the other end is connected and we have something + to send, then we send it and reset the outBuffer. + If the other end is closed and we are signaling errors, do so." + + (outNextToWrite > 1 and: [ socket isOtherEndClosed not ]) ifTrue: [ + [ socket sendData: outBuffer count: outNextToWrite - 1 ] + on: NetworkError + do: [ :ex | + shouldSignal + ifTrue: [ ex pass ] + ifFalse: [ "swallow" ] ]. + outNextToWrite := 1 ] +] diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index 2e83841e..d545543f 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -325,7 +325,7 @@ RabbitMQClientTest >> testPublishingMessageWhenConnectionIsTemporallyLost [ '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to CONNECTION_FORCED - CloseConnectionsTest' . '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to (connection closed while sending data|Data send timed out.)' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to (connection closed while sending data|send data timeout; data not sent)' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } ]. From 78dd652678486b3866ac4911f1890c210292859d Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 12 Aug 2024 15:33:32 -0300 Subject: [PATCH 43/65] Move the fixed #flush implementation to an extension loaded only on Pharo < 12 versions --- .../SocketStream.extension.st} | 11 +++------- .../Ansible-Pharo-Pending-Patches/package.st | 1 + .../AmqpConnection.class.st | 22 ++++++++++--------- .../BaselineOfAnsible.class.st | 13 ++++++++++- 4 files changed, 28 insertions(+), 19 deletions(-) rename source/{Ansible-Protocol-Core/AmqpSocketStream.class.st => Ansible-Pharo-Pending-Patches/SocketStream.extension.st} (68%) create mode 100644 source/Ansible-Pharo-Pending-Patches/package.st diff --git a/source/Ansible-Protocol-Core/AmqpSocketStream.class.st b/source/Ansible-Pharo-Pending-Patches/SocketStream.extension.st similarity index 68% rename from source/Ansible-Protocol-Core/AmqpSocketStream.class.st rename to source/Ansible-Pharo-Pending-Patches/SocketStream.extension.st index 14819b8c..0c766a44 100644 --- a/source/Ansible-Protocol-Core/AmqpSocketStream.class.st +++ b/source/Ansible-Pharo-Pending-Patches/SocketStream.extension.st @@ -1,12 +1,7 @@ -Class { - #name : 'AmqpSocketStream', - #superclass : 'SocketStream', - #category : 'Ansible-Protocol-Core', - #package : 'Ansible-Protocol-Core' -} +Extension { #name : 'SocketStream' } -{ #category : 'control' } -AmqpSocketStream >> flush [ +{ #category : '*Ansible-Pharo-Pending-Patches' } +SocketStream >> flush [ "If the other end is connected and we have something to send, then we send it and reset the outBuffer. If the other end is closed and we are signaling errors, do so." diff --git a/source/Ansible-Pharo-Pending-Patches/package.st b/source/Ansible-Pharo-Pending-Patches/package.st new file mode 100644 index 00000000..eab03e43 --- /dev/null +++ b/source/Ansible-Pharo-Pending-Patches/package.st @@ -0,0 +1 @@ +Package { #name : 'Ansible-Pharo-Pending-Patches' } diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 29899a54..c9b828ab 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -268,18 +268,20 @@ AmqpConnection >> nextFrame [ { #category : 'opening' } AmqpConnection >> open [ - codec := AmqpCodec on: ( ( AmqpSocketStream on: socket ) - noTimeout; - binary; - yourself ). + codec := AmqpCodec + on: + ((SocketStream on: socket) + noTimeout; + binary; + yourself). - self - sendProtocolHeader; - startConnection; - tuneConnection; - openConnection. + self + sendProtocolHeader; + startConnection; + tuneConnection; + openConnection. - heartbeatSender startBeatingEvery: parameters heartbeat + heartbeatSender startBeatingEvery: parameters heartbeat ] { #category : 'private-opening' } diff --git a/source/BaselineOfAnsible/BaselineOfAnsible.class.st b/source/BaselineOfAnsible/BaselineOfAnsible.class.st index 5c4779a6..60b33b1a 100644 --- a/source/BaselineOfAnsible/BaselineOfAnsible.class.st +++ b/source/BaselineOfAnsible/BaselineOfAnsible.class.st @@ -55,7 +55,18 @@ BaselineOfAnsible >> setUpDependencies: spec [ { #category : 'baselines' } BaselineOfAnsible >> setUpDeploymentPackages: spec [ - + + spec + for: #('pharo7.x' 'pharo8.x' 'pharo9.x' 'pharo10.x' 'pharo11.x') + do: [ + spec + package: 'Ansible-Pharo-Pending-Patches'; + group: 'RabbitMQ' with: 'Ansible-Pharo-Pending-Patches'. + spec + package: 'Ansible-RabbitMQ' + with: [ spec requires: #( 'Ansible-Pharo-Pending-Patches' 'Ansible-Protocol-091' 'Launchpad-Deployment' 'Hyperspace-Deployment' ) ]; + group: 'RabbitMQ' with: 'Ansible-RabbitMQ' ]. + spec package: 'Ansible-Protocol-Core'; group: 'Deployment-08' with: 'Ansible-Protocol-Core'; From 3f3306374a60fa9f248a0b18148ffcf548906ab7 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 12 Aug 2024 16:14:01 -0300 Subject: [PATCH 44/65] Correct the deprecated message --- .../AmqpChannel.extension.st | 13 +++++++++ .../RabbitMQPublisher.extension.st | 13 --------- .../RabbitMQClientTest.class.st | 4 +-- .../Ansible-RabbitMQ/AmqpChannel.extension.st | 27 +++++++++++++++---- .../RabbitMQPublisher.class.st | 14 +++++----- 5 files changed, 44 insertions(+), 27 deletions(-) create mode 100644 source/Ansible-Deprecated-v3/AmqpChannel.extension.st delete mode 100644 source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st diff --git a/source/Ansible-Deprecated-v3/AmqpChannel.extension.st b/source/Ansible-Deprecated-v3/AmqpChannel.extension.st new file mode 100644 index 00000000..b31e3f7c --- /dev/null +++ b/source/Ansible-Deprecated-v3/AmqpChannel.extension.st @@ -0,0 +1,13 @@ +Extension { #name : 'AmqpChannel' } + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ + + self + deprecated: 'Use confirmPublicationWith:otherwise:' + transformWith: + '`@receiver confirmMessagesPublicationWith: `@anAckBlock andThoseNotProcessedWith: `@aNackBlock' + -> '`@receiver onPublicationConfirmationDo: `@anAckBlock onRejectionDo: `@aNackBlock'. + + self onPublicationConfirmationDo: anAckBlock onRejectionDo: aNackBlock +] diff --git a/source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st b/source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st deleted file mode 100644 index 9fff0f71..00000000 --- a/source/Ansible-Deprecated-v3/RabbitMQPublisher.extension.st +++ /dev/null @@ -1,13 +0,0 @@ -Extension { #name : 'RabbitMQPublisher' } - -{ #category : '*Ansible-Deprecated-v3' } -RabbitMQPublisher >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWith: aNackBlock [ - - self - deprecated: 'Use confirmPublicationWith:otherwise:' - transformWith: - '`@receiver confirmMessagesPublicationWith: `@anAckBlock andThoseNotProcessedWith: `@aNackBlock' - -> '`@receiver confirmPublicationWith: `@anAckBlock otherwise: `@aNackBlock'. - - self confirmPublicationWith: anAckBlock otherwise: aNackBlock -] diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index d545543f..1987c208 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -76,8 +76,8 @@ RabbitMQClientTest >> assertQueueStatusAfterPublishing: messagesToSend on: queue messageWasPublished := false. publisher - confirmPublicationWith: [ :command | messageWasPublished := true ] - otherwise: [ :command | self fail ]. + onPublicationConfirmationDo: [ :command | messageWasPublished := true ] + onRejectionDo: [ :command | self fail ]. queue := publisher channel queueDeclare: queueName. diff --git a/source/Ansible-RabbitMQ/AmqpChannel.extension.st b/source/Ansible-RabbitMQ/AmqpChannel.extension.st index 83252a90..a0e241ad 100644 --- a/source/Ansible-RabbitMQ/AmqpChannel.extension.st +++ b/source/Ansible-RabbitMQ/AmqpChannel.extension.st @@ -1,11 +1,28 @@ Extension { #name : 'AmqpChannel' } { #category : '*Ansible-RabbitMQ' } -AmqpChannel >> confirmPublicationWith: anAckBlock otherwise: aNackBlock [ +AmqpChannel >> beInConfirmMode [ - self rpc: protocolVersion confirmSelectMethod new. + self rpc: protocolVersion confirmSelectMethod new +] + +{ #category : '*Ansible-RabbitMQ' } +AmqpChannel >> onPublicationConfirmationDo: anAckBlock [ + + handler mapEvent: protocolVersion basicAckMethod to: anAckBlock selector: #value: +] + +{ #category : '*Ansible-RabbitMQ' } +AmqpChannel >> onPublicationConfirmationDo: anAckBlock onRejectionDo: aNackBlock [ + + self + beInConfirmMode; + onPublicationConfirmationDo: anAckBlock; + onPublicationRejectionDo: aNackBlock +] + +{ #category : '*Ansible-RabbitMQ' } +AmqpChannel >> onPublicationRejectionDo: aNackBlock [ - handler - mapEvent: protocolVersion basicAckMethod to: anAckBlock selector: #value:; - mapEvent: protocolVersion basicNackMethod to: aNackBlock selector: #value: + handler mapEvent: protocolVersion basicNackMethod to: aNackBlock selector: #value: ] diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index b2f1a3d2..d62dcaf8 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -26,13 +26,6 @@ RabbitMQPublisher class >> configuredBy: aConfigurationAction [ ^ self new initializeConfiguredBy: options ] -{ #category : 'configuring' } -RabbitMQPublisher >> confirmPublicationWith: anAckBlock otherwise: aNackBlock [ - - channelConfigurationCommands add: [ :theChannel | - theChannel confirmPublicationWith: anAckBlock otherwise: aNackBlock ] -] - { #category : 'initialization' } RabbitMQPublisher >> ensureChannelOpen [ @@ -48,6 +41,13 @@ RabbitMQPublisher >> initializeConfiguredBy: anOptionsDictionary [ channelConfigurationCommands := OrderedCollection new ] +{ #category : 'configuring' } +RabbitMQPublisher >> onPublicationConfirmationDo: anAckBlock onRejectionDo: aNackBlock [ + + channelConfigurationCommands add: [ :theChannel | + theChannel onPublicationConfirmationDo: anAckBlock onRejectionDo: aNackBlock ] +] + { #category : 'publishing' } RabbitMQPublisher >> publish: aMessageCollection onQueueNamed: aQueueName [ From 75ddd2faf8a9b8ea0e210efc0998772e1a91dbb6 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 12 Aug 2024 17:01:23 -0300 Subject: [PATCH 45/65] Rename RabbitMQWorker instantiation Refactor RabbitMQPublisher delivery mode message --- .../RabbitMQTextReverser.class.st | 2 +- .../RabbitMQPublisher.class.st | 61 ++++++++++--------- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 16 ++--- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st index 20d47ae6..d361e5ee 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQTextReverser.class.st @@ -27,7 +27,7 @@ RabbitMQTextReverser >> initializeWorkingWith: aTestCase [ at: 'process' put: 'Text Reverser Worker'; yourself ) ] - doingWithPayload: [ :payload | aTestCase storeText: payload utf8Decoded reversed ] + processingPayloadWith: [ :payload | aTestCase storeText: payload utf8Decoded reversed ] ] { #category : 'accessing' } diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index d62dcaf8..7ed83160 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -48,6 +48,12 @@ RabbitMQPublisher >> onPublicationConfirmationDo: anAckBlock onRejectionDo: aNac theChannel onPublicationConfirmationDo: anAckBlock onRejectionDo: aNackBlock ] ] +{ #category : 'private - configuring' } +RabbitMQPublisher >> persistentDeliveryMode [ + + ^ connection protocolClass basicPropertiesClass new deliveryMode: 2 +] + { #category : 'publishing' } RabbitMQPublisher >> publish: aMessageCollection onQueueNamed: aQueueName [ @@ -57,34 +63,33 @@ RabbitMQPublisher >> publish: aMessageCollection onQueueNamed: aQueueName [ { #category : 'publishing' } RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ - | tryToPublishMessage | - - self ensureChannelOpen. - tryToPublishMessage := [ - channel - basicPublish: aMessage utf8Encoded - exchange: '' - routingKey: aQueueName - properties: - ( connection protocolClass basicPropertiesClass new deliveryMode: 2 ) - ]. - - self shouldLogDebuggingInfo then: [ - LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message published' with: [ :data | - data - at: #messagePublished put: aMessage; - at: #routingKey put: aQueueName; - at: #connectionDescription put: connection connectionPairsDescription - ] - ]. - - tryToPublishMessage - on: self connectivityErrors - do: [ :signal | - connection hardCloseDescribedWith: signal messageText. - self ensureChannelOpen. - signal return: tryToPublishMessage value - ] + | tryToPublishMessage | + + self ensureChannelOpen. + tryToPublishMessage := [ + channel + basicPublish: aMessage utf8Encoded + exchange: '' + routingKey: aQueueName + properties: self persistentDeliveryMode + ]. + + self shouldLogDebuggingInfo then: [ + LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message published' with: [ :data | + data + at: #messagePublished put: aMessage; + at: #routingKey put: aQueueName; + at: #connectionDescription put: connection connectionPairsDescription + ] + ]. + + tryToPublishMessage + on: self connectivityErrors + do: [ :signal | + connection hardCloseDescribedWith: signal messageText. + self ensureChannelOpen. + signal return: tryToPublishMessage value + ] ] { #category : 'connecting' } diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 6e3de2c3..10d90246 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -21,14 +21,6 @@ Class { #package : 'Ansible-RabbitMQ' } -{ #category : 'testing' } -RabbitMQWorker class >> configuredBy: aConfigurationAction doingWithPayload: aPayloadProcessor [ - - ^ self - configuredBy: aConfigurationAction - processingMessagesWith: [ :message | aPayloadProcessor value: message body ] -] - { #category : 'testing' } RabbitMQWorker class >> configuredBy: aConfigurationAction processingMessagesWith: aMessageProcessor [ @@ -41,6 +33,14 @@ RabbitMQWorker class >> configuredBy: aConfigurationAction processingMessagesWit ^ self new initializeConfiguredBy: options processingMessagesWith: aMessageProcessor ] +{ #category : 'testing' } +RabbitMQWorker class >> configuredBy: aConfigurationAction processingPayloadWith: aPayloadProcessor [ + + ^ self + configuredBy: aConfigurationAction + processingMessagesWith: [ :message | aPayloadProcessor value: message body ] +] + { #category : 'private' } RabbitMQWorker >> declareQueueInChannel [ From 6013e94e7d73c91178e268cd61635125ea6bd245 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 12 Aug 2024 18:38:10 -0300 Subject: [PATCH 46/65] Update migration guide and docs with new RabbitMQ Clients --- docs/MigrationGuide.md | 61 ++++++++++++++++++++++++++++- docs/README.md | 7 ++++ docs/tutorials/PublishSubscribe.md | 2 +- docs/tutorials/RabbitMQPublisher.md | 17 ++++++++ docs/tutorials/RabbitMQWorker.md | 19 +++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 docs/tutorials/RabbitMQPublisher.md create mode 100644 docs/tutorials/RabbitMQWorker.md diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md index d8e74e34..7b7956eb 100644 --- a/docs/MigrationGuide.md +++ b/docs/MigrationGuide.md @@ -2,4 +2,63 @@ ## Migration from v2 to v3 -To achieve this, manually load the package `Ansible-Deprecated-V3`. +* Manually load the package `Ansible-Deprecated-V3` that has transformation of deprecated messages. + +* `RabbitMQWorker` has been refactored to be used by composition instead of inheritance: +The classes that subclassified it implemented the subclass responsibility `#configureConnection:` and `#process: payload` should be changed to instantiate `RabbitMQWorker` as to pass that process logic to the `processingPayloadWith:` collaborator. + +For instance, + +``` +Class { + #name : 'RabbitMQTextReverser', + #superclass : 'RabbitMQWorker', + #instVars : [ + 'testCase', + ] +} + +{ #category : 'initialization' } +RabbitMQTextReverser >> initializeWorkingWith: aTestCase + + testCase := aTestCase + +{ #category : 'private' } +RabbitMQTextReverser >> #configureConnection: builder + + builder hostname: 'localhost'. + builder portNumber: 5672. + builder username: 'guest'. + builder password: 'guest'. + +{ #category : 'private' } +RabbitMQTextReverser >> process: payload + + testCase storeText: payload utf8Decoded reversed +``` + +Should be refactored to + +``` +Class { + #name : 'RabbitMQTextReverser', + #superclass : 'Object', + #instVars : [ + 'worker' + ] +} + +RabbitMQTextReverser >> initializeWorkingWith: aTestCase + + worker := RabbitMQWorker + configuredBy: [ :options | + options + at: #hostname 'localhost';. + at: #port 5672;. + at: #username 'guest';. + at: #password 'guest' + ] + processingMessagesWith: [ :payload | aTestCase storeText: payload utf8Decoded reversed ]. + + worker start +``` \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index f38738ab..cb217c87 100644 --- a/docs/README.md +++ b/docs/README.md @@ -44,6 +44,13 @@ They are not a rewrite but rather my interpretation. reading it to get a complete understanding. They also provide this [great tool](http://tryrabbitmq.com) to help you explore different messaging patterns. +## Use RabbitMQ clients reifications + +We provide two objects to simplify the instantiations of a publisher and a consumer: + +1. [RabbitMQPublisher](tutorials/RabbitMQPublisher.md) +2. [RabbitMQWorker](tutorials/RabbitMQWorker.md) + --- To use the project as a dependency of your project, take a look at: diff --git a/docs/tutorials/PublishSubscribe.md b/docs/tutorials/PublishSubscribe.md index 9fd17fe6..1c0899e5 100644 --- a/docs/tutorials/PublishSubscribe.md +++ b/docs/tutorials/PublishSubscribe.md @@ -70,7 +70,7 @@ logger name: 'Transcript logger'. logger resume ``` -## Receiveing notifications +## Receiving notifications Here's the script to spawn a process that will pop up a toast notification on every log message received diff --git a/docs/tutorials/RabbitMQPublisher.md b/docs/tutorials/RabbitMQPublisher.md new file mode 100644 index 00000000..c318a0f7 --- /dev/null +++ b/docs/tutorials/RabbitMQPublisher.md @@ -0,0 +1,17 @@ +# RabbitMQPublisher + +This object will connect to an AMQP channel and knows how to publish messages to the specified queue for further processing. + +Accepts the following options: + +| Attribute name | Description | Optional/Mandatory | Default value | +| ---------------|-------------|--------------------|---------------| +| #hostname | Hostname of the rabbitmq broker | Optional | localhost | +| #port | Port numbre of the rabbitmq broker | Optional | 5672 | +| #username | Username of the rabbitmq broker | Optional | guest | +| #password | Username of the rabbitmq broker | Optional | guest | +| #maximumConnectionAttemps | Amount of retries when connecting to the broker fails | Optional | 3 | +| #timeSlotBetweenConnectionRetriesInMs | Time duration between retry attempts determined by using the exponential backoff algorithm | Optional | 3000 | +| #enableDebuggingLogs | A boolean indicating whether to log debugging events | Optional | false | +| #extraClientProperties | A dictionary with keys and values to set the (client properties)[https://www.rabbitmq.com/docs/connections#capabilities] | Optional | Empty | +| #retry | A block that can configure the internal `Retry` instance | Optional | `[]` | \ No newline at end of file diff --git a/docs/tutorials/RabbitMQWorker.md b/docs/tutorials/RabbitMQWorker.md new file mode 100644 index 00000000..788ff67c --- /dev/null +++ b/docs/tutorials/RabbitMQWorker.md @@ -0,0 +1,19 @@ +# RabbitMQWorker + +This object will connect to an AMQP channel and knows how to consume messages from a specified queue for processing. + +Accepts the following options: + +| Attribute name | Description | Optional/Mandatory | Default value | +| ---------------|-------------|--------------------|---------------| +| #hostname | Hostname of the rabbitmq broker | Optional | localhost | +| #port | Port numbre of the rabbitmq broker | Optional | 5672 | +| #username | Username of the rabbitmq broker | Optional | guest | +| #password | Username of the rabbitmq broker | Optional | guest | +| #maximumConnectionAttemps | Amount of retries when connecting to the broker fails | Optional | 3 | +| #timeSlotBetweenConnectionRetriesInMs | Time duration between retry attempts determined by using the exponential backoff algorithm | Optional | 3000 | +| #enableDebuggingLogs | A boolean indicating whether to log debugging events | Optional | false | +| #extraClientProperties | A dictionary with keys and values to set the (client properties)[https://www.rabbitmq.com/docs/connections#capabilities] | Optional | Empty | +| #retry | A block that can configure the internal `Retry` instance | Optional | `[]` | +| #queueName | Queue name where to consume from | Mandatory | | +| #queueDurable | When false sets the [queue durability](https://www.rabbitmq.com/docs/queues#durability) to transient, otherwise will be durable | Optional | true | From d8d1ea4c16e8031cdfb2af2cf25e9eab0ca1ccc3 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 12 Aug 2024 18:44:42 -0300 Subject: [PATCH 47/65] Update timeframeBetweenAttempts default value --- source/Ansible-RabbitMQ/RabbitMQClient.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 77796cb3..e098419d 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -134,7 +134,7 @@ RabbitMQClient >> stop [ { #category : 'private - accessing' } RabbitMQClient >> timeframeBetweenAttempts [ - ^ Duration milliSeconds: ( options at: #timeSlotBetweenConnectionRetriesInMs ifAbsent: [ 300 ] ) + ^ Duration milliSeconds: ( options at: #timeSlotBetweenConnectionRetriesInMs ifAbsent: [ 3000 ] ) ] { #category : 'private - connecting' } From e9eca3c91f1f6796b4568a79252e85a61e525ce8 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 12 Aug 2024 19:42:34 -0300 Subject: [PATCH 48/65] Add support of debugging info in RabbitMQWorker --- .../RabbitMQClientTest.class.st | 20 +++++++++++++++- .../RabbitMQPublisher.class.st | 24 +++++++++++-------- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 16 ++++++++++++- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index 1987c208..d784d0cf 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -49,7 +49,8 @@ RabbitMQClientTest >> assertQueueStatusAfterConsuming: messagesToSend from: queu configuredBy: [ :options | options at: #queueName put: queueName; - at: #queueDurable put: false + at: #queueDurable put: false; + at: #enableDebuggingLogs put: true ] processingMessagesWith: [ :messageReceived | messageWasConsumed := true. @@ -267,6 +268,23 @@ RabbitMQClientTest >> testDebuggingLogsEnabled [ '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to Normal shutdown' } ] +{ #category : 'tests' } +RabbitMQClientTest >> testDebuggingLogsEnabledInWorker [ + + | queueName messagesToSend | + queueName := self queueName. + messagesToSend := 'Do it!'. + + self assertQueueStatusAfterPublishing: messagesToSend on: queueName. + + self + runMemoryLoggerDuring: [ self assertQueueStatusAfterConsuming: messagesToSend from: queueName ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[DEBUG\] RabbitMQ message consumed \{"messageConsumed"\:"Do it!","routingKey"\:"tasks-for-testDebuggingLogsEnabledInWorker","connectionDescription"\:"localhost\:(\d+)->localhost\:5672"\}' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to Normal shutdown' } +] + { #category : 'tests' } RabbitMQClientTest >> testDebuggingLogsTurnedOff [ diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 7ed83160..046cb3f6 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -41,6 +41,19 @@ RabbitMQPublisher >> initializeConfiguredBy: anOptionsDictionary [ channelConfigurationCommands := OrderedCollection new ] +{ #category : 'private - logging' } +RabbitMQPublisher >> logDebuggingInfoFor: aMessage publishedTo: aQueueName [ + + self shouldLogDebuggingInfo then: [ + LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message published' with: [ :data | + data + at: #messagePublished put: aMessage; + at: #routingKey put: aQueueName; + at: #connectionDescription put: connection connectionPairsDescription + ] + ] +] + { #category : 'configuring' } RabbitMQPublisher >> onPublicationConfirmationDo: anAckBlock onRejectionDo: aNackBlock [ @@ -64,7 +77,6 @@ RabbitMQPublisher >> publish: aMessageCollection onQueueNamed: aQueueName [ RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ | tryToPublishMessage | - self ensureChannelOpen. tryToPublishMessage := [ channel @@ -73,15 +85,7 @@ RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ routingKey: aQueueName properties: self persistentDeliveryMode ]. - - self shouldLogDebuggingInfo then: [ - LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message published' with: [ :data | - data - at: #messagePublished put: aMessage; - at: #routingKey put: aQueueName; - at: #connectionDescription put: connection connectionPairsDescription - ] - ]. + self logDebuggingInfoFor: aMessage publishedTo: aQueueName. tryToPublishMessage on: self connectivityErrors diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 10d90246..44dd0620 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -62,7 +62,20 @@ RabbitMQWorker >> initializeConfiguredBy: anOptionsDictionary processingMessages messageProcessor := aMessageProcessor ] -{ #category : 'private' } +{ #category : 'private - logging' } +RabbitMQWorker >> logDebuggingInfoFor: aMessage [ + + self shouldLogDebuggingInfo then: [ + LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message consumed' with: [ :data | + data + at: #messageConsumed put: aMessage body utf8Decoded; + at: #routingKey put: self queueName; + at: #connectionDescription put: connection connectionPairsDescription + ] + ] +] + +{ #category : 'private - logging' } RabbitMQWorker >> logDisconnectionDueTo: anError [ LogRecord emitError: @@ -89,6 +102,7 @@ RabbitMQWorker >> startConsumingFromQueue [ channel prefetchCount: 1. channel consumeFrom: self queueName applying: [ :message | + self logDebuggingInfoFor: message. messageProcessor value: message. channel basicAck: message method deliveryTag ] From 205408af9cbe6cc8a405074d8409ebf06ea50805 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 13 Aug 2024 11:02:21 -0300 Subject: [PATCH 49/65] Fix markdown linter errors --- docs/MigrationGuide.md | 58 ++++++++++++++++------------- docs/README.md | 3 +- docs/tutorials/RabbitMQPublisher.md | 12 +++--- docs/tutorials/RabbitMQWorker.md | 10 +++-- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md index 7b7956eb..ea57ca95 100644 --- a/docs/MigrationGuide.md +++ b/docs/MigrationGuide.md @@ -2,63 +2,69 @@ ## Migration from v2 to v3 -* Manually load the package `Ansible-Deprecated-V3` that has transformation of deprecated messages. +* Manually load the package `Ansible-Deprecated-V3` that has transformation of +deprecated messages. -* `RabbitMQWorker` has been refactored to be used by composition instead of inheritance: -The classes that subclassified it implemented the subclass responsibility `#configureConnection:` and `#process: payload` should be changed to instantiate `RabbitMQWorker` as to pass that process logic to the `processingPayloadWith:` collaborator. +* `RabbitMQWorker` has been refactored to be used by composition instead of +inheritance: The classes that subclassified it implemented the subclass +responsibility `#configureConnection:` and `#process: payload` should be +changed to instantiate `RabbitMQWorker` as to pass that process logic to the +`processingPayloadWith:` collaborator. -For instance, +For instance, -``` +```smalltalk Class { - #name : 'RabbitMQTextReverser', - #superclass : 'RabbitMQWorker', - #instVars : [ + #name : 'RabbitMQTextReverser', + #superclass : 'RabbitMQWorker', + #instVars : [ 'testCase', ] } { #category : 'initialization' } -RabbitMQTextReverser >> initializeWorkingWith: aTestCase +RabbitMQTextReverser >> initializeWorkingWith: aTestCase testCase := aTestCase { #category : 'private' } -RabbitMQTextReverser >> #configureConnection: builder - +RabbitMQTextReverser >> #configureConnection: builder + builder hostname: 'localhost'. - builder portNumber: 5672. - builder username: 'guest'. - builder password: 'guest'. + builder portNumber: 5672. + builder username: 'guest'. + builder password: 'guest'. { #category : 'private' } -RabbitMQTextReverser >> process: payload - +RabbitMQTextReverser >> process: payload + testCase storeText: payload utf8Decoded reversed ``` Should be refactored to -``` +```smalltalk Class { - #name : 'RabbitMQTextReverser', - #superclass : 'Object', - #instVars : [ + #name : 'RabbitMQTextReverser', + #superclass : 'Object', + #instVars : [ 'worker' ] } -RabbitMQTextReverser >> initializeWorkingWith: aTestCase +RabbitMQTextReverser >> initializeWorkingWith: aTestCase worker := RabbitMQWorker configuredBy: [ :options | - options + options at: #hostname 'localhost';. at: #port 5672;. - at: #username 'guest';. - at: #password 'guest' + at: #username 'guest';. + at: #password 'guest' ] - processingMessagesWith: [ :payload | aTestCase storeText: payload utf8Decoded reversed ]. + processingMessagesWith: [ :payload | + aTestCase storeText: payload utf8Decoded reversed + ]. worker start -``` \ No newline at end of file +``` diff --git a/docs/README.md b/docs/README.md index cb217c87..c675eb23 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,7 +46,8 @@ They are not a rewrite but rather my interpretation. ## Use RabbitMQ clients reifications -We provide two objects to simplify the instantiations of a publisher and a consumer: +We provide two objects to simplify the instantiations of a publisher and a +consumer: 1. [RabbitMQPublisher](tutorials/RabbitMQPublisher.md) 2. [RabbitMQWorker](tutorials/RabbitMQWorker.md) diff --git a/docs/tutorials/RabbitMQPublisher.md b/docs/tutorials/RabbitMQPublisher.md index c318a0f7..5d234226 100644 --- a/docs/tutorials/RabbitMQPublisher.md +++ b/docs/tutorials/RabbitMQPublisher.md @@ -1,9 +1,11 @@ -# RabbitMQPublisher +# RabbitMQPublisher -This object will connect to an AMQP channel and knows how to publish messages to the specified queue for further processing. +This object will connect to an AMQP channel and knows how to publish messages +to the specified queue for further processing. -Accepts the following options: +Accepts the following options: + | Attribute name | Description | Optional/Mandatory | Default value | | ---------------|-------------|--------------------|---------------| | #hostname | Hostname of the rabbitmq broker | Optional | localhost | @@ -13,5 +15,5 @@ Accepts the following options: | #maximumConnectionAttemps | Amount of retries when connecting to the broker fails | Optional | 3 | | #timeSlotBetweenConnectionRetriesInMs | Time duration between retry attempts determined by using the exponential backoff algorithm | Optional | 3000 | | #enableDebuggingLogs | A boolean indicating whether to log debugging events | Optional | false | -| #extraClientProperties | A dictionary with keys and values to set the (client properties)[https://www.rabbitmq.com/docs/connections#capabilities] | Optional | Empty | -| #retry | A block that can configure the internal `Retry` instance | Optional | `[]` | \ No newline at end of file +| #extraClientProperties | A dictionary with keys and values to set the [client properties](https://www.rabbitmq.com/docs/connections#capabilities) |Optional | Empty | +| #retry | A block that can configure the internal `Retry` instance | Optional | `[]` | diff --git a/docs/tutorials/RabbitMQWorker.md b/docs/tutorials/RabbitMQWorker.md index 788ff67c..424065dc 100644 --- a/docs/tutorials/RabbitMQWorker.md +++ b/docs/tutorials/RabbitMQWorker.md @@ -1,9 +1,11 @@ -# RabbitMQWorker +# RabbitMQWorker -This object will connect to an AMQP channel and knows how to consume messages from a specified queue for processing. +This object will connect to an AMQP channel and knows how to consume messages +from a specified queue for processing. -Accepts the following options: +Accepts the following options: + | Attribute name | Description | Optional/Mandatory | Default value | | ---------------|-------------|--------------------|---------------| | #hostname | Hostname of the rabbitmq broker | Optional | localhost | @@ -13,7 +15,7 @@ Accepts the following options: | #maximumConnectionAttemps | Amount of retries when connecting to the broker fails | Optional | 3 | | #timeSlotBetweenConnectionRetriesInMs | Time duration between retry attempts determined by using the exponential backoff algorithm | Optional | 3000 | | #enableDebuggingLogs | A boolean indicating whether to log debugging events | Optional | false | -| #extraClientProperties | A dictionary with keys and values to set the (client properties)[https://www.rabbitmq.com/docs/connections#capabilities] | Optional | Empty | +| #extraClientProperties | A dictionary with keys and values to set the [client properties](https://www.rabbitmq.com/docs/connections#capabilities)| Optional | Empty | | #retry | A block that can configure the internal `Retry` instance | Optional | `[]` | | #queueName | Queue name where to consume from | Mandatory | | | #queueDurable | When false sets the [queue durability](https://www.rabbitmq.com/docs/queues#durability) to transient, otherwise will be durable | Optional | true | From 30e5053867a52a5cc9bc8fdcb8e898e277c7630c Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 13 Aug 2024 15:54:18 -0300 Subject: [PATCH 50/65] Include deliveryTag in the worker debugging logs --- source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st | 2 +- source/Ansible-RabbitMQ/RabbitMQWorker.class.st | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index d784d0cf..f24ebe4c 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -281,7 +281,7 @@ RabbitMQClientTest >> testDebuggingLogsEnabledInWorker [ runMemoryLoggerDuring: [ self assertQueueStatusAfterConsuming: messagesToSend from: queueName ] assertingLogRecordsMatchRegexes: { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[DEBUG\] RabbitMQ message consumed \{"messageConsumed"\:"Do it!","routingKey"\:"tasks-for-testDebuggingLogsEnabledInWorker","connectionDescription"\:"localhost\:(\d+)->localhost\:5672"\}' . + '\[DEBUG\] RabbitMQ message consumed \{"deliveryTag"\:1,"messageConsumed"\:"Do it!","routingKey"\:"tasks-for-testDebuggingLogsEnabledInWorker","connectionDescription"\:"localhost\:(\d+)->localhost\:5672"\}' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to Normal shutdown' } ] diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 44dd0620..ffa457a1 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -68,6 +68,7 @@ RabbitMQWorker >> logDebuggingInfoFor: aMessage [ self shouldLogDebuggingInfo then: [ LogRecord emitStructuredDebuggingInfo: 'RabbitMQ message consumed' with: [ :data | data + at: #deliveryTag put: aMessage method deliveryTag; at: #messageConsumed put: aMessage body utf8Decoded; at: #routingKey put: self queueName; at: #connectionDescription put: connection connectionPairsDescription From 2d0514670ff97791b8db730d5a8a82314d68def4 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Mon, 26 Aug 2024 21:14:04 -0300 Subject: [PATCH 51/65] Refactor AmqpChannelHandler instance variables --- .../AmqpChannelHandler.class.st | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st index 98de956c..cdc7bada 100644 --- a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st @@ -5,12 +5,12 @@ Class { 'connection', 'channelNumber', 'inbound', - 'state', 'closeReason', 'asyncMap', 'currentCommand', 'remainingBytes', - 'bodyPieces' + 'bodyPieces', + 'nextFrameHandler' ], #category : 'Ansible-Protocol-Core', #package : 'Ansible-Protocol-Core' @@ -26,7 +26,7 @@ AmqpChannelHandler >> channelNumber [ AmqpChannelHandler >> checkBodyCompletion [ remainingBytes > 0 - ifTrue: [ state := #handleBodyFrame: ] + ifTrue: [ nextFrameHandler := #handleBodyFrame: ] ifFalse: [ remainingBytes < 0 ifTrue: [ AmqpProtocolSyntaxError signal: 'Received body overrun' ]. currentCommand body: bodyPieces contents. @@ -58,10 +58,10 @@ AmqpChannelHandler >> connection: anObject [ AmqpChannelHandler >> dispatchCommand [ ( asyncMap includesKey: currentCommand method class ) - ifTrue: [ | a | + ifTrue: [ | messageSend | - a := asyncMap at: currentCommand method class. - a key perform: a value with: currentCommand + messageSend := asyncMap at: currentCommand method class. + messageSend value: currentCommand ] ifFalse: [ inbound add: currentCommand ]. self resetState @@ -87,7 +87,7 @@ AmqpChannelHandler >> handleBodyFrame: frame [ { #category : 'handling' } AmqpChannelHandler >> handleFrame: frame [ - self perform: state with: frame + self perform: nextFrameHandler with: frame ] { #category : 'handling' } @@ -98,7 +98,7 @@ AmqpChannelHandler >> handleMethodFrame: frame [ currentCommand := AmqpCommand new. currentCommand method: frame method. frame method hasContents - ifTrue: [ state := #handlePropertiesFrame: ] + ifTrue: [ nextFrameHandler := #handlePropertiesFrame: ] ifFalse: [ self dispatchCommand ] ] @@ -136,13 +136,13 @@ AmqpChannelHandler >> internalClose: method [ { #category : 'handling' } AmqpChannelHandler >> mapEvent: evtClass to: receiver selector: aSymbol [ - asyncMap at: evtClass put: receiver -> aSymbol + asyncMap at: evtClass put: ( MessageSend receiver: receiver selector: aSymbol ) ] { #category : 'handling' } AmqpChannelHandler >> resetState [ - state := #handleMethodFrame:. + nextFrameHandler := #handleMethodFrame:. currentCommand := nil. remainingBytes := 0. bodyPieces := nil From ff7ce847483af7902bf3e0ef997b9bf54c53b160 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 14:15:40 -0300 Subject: [PATCH 52/65] Refactor the SocketConnectionStatus hierarchy and improve some handling on socket status --- .../AmqpChannelHandler.class.st | 2 +- .../AmqpConnection.class.st | 15 +++++++---- ...ass.st => ClosedSocketConnection.class.st} | 8 +++--- .../FailedSocketConnection.class.st | 27 ------------------- 4 files changed, 15 insertions(+), 37 deletions(-) rename source/Ansible-Protocol-Core/{HealthyClosedSocketConnection.class.st => ClosedSocketConnection.class.st} (59%) delete mode 100644 source/Ansible-Protocol-Core/FailedSocketConnection.class.st diff --git a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st index cdc7bada..8e895066 100644 --- a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st @@ -169,7 +169,7 @@ AmqpChannelHandler >> waitForReply: acceptableReplies [ self ensureOpen. currentConnection := connection mainLoopCycle. - currentConnection socketConnectionStatus + currentConnection whenConnected: [ ] whenNot: [ :error | AmqpDisconnectedError signal: error ] ]. diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index c9b828ab..dcc288a2 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -45,15 +45,20 @@ AmqpConnection >> becomeCloseAfter: aBlock [ isOpen ifTrue: [ [ closingMethod := aBlock value. - channels valuesDo: [ :ch | ch internalClose: closingMethod ]. + channels valuesDo: [ :ch | ch internalClose: closingMethod ] ] ensure: [ LogRecord emitInfo: ( 'AMQP connection <1s> closed due to <2s>' expandMacrosWith: self connectionPairsDescription with: closingMethod replyText ). heartbeatSender stop. - socket close. + [ socket close ] + on: SocketError + do: [ :error | + " If the socket was unexpectedly closed, trying to close it again + will raise a SocketError that we want to silence " + error return ]. isOpen := false. - socketConnectionStatus := HealthyClosedSocketConnection dueTo: closingMethod replyText + socketConnectionStatus := ClosedSocketConnection dueTo: closingMethod replyText ] ] ] @@ -91,7 +96,7 @@ AmqpConnection >> codec [ AmqpConnection >> connectionPairsDescription [ ^ 'localhost:<1p>-><2s>:<3p>' - expandMacrosWith: socket port + expandMacrosWith: ([socket port] on: SocketError do: [ :error | error return: 0 ]) with: hostname with: portNumber ] @@ -175,7 +180,7 @@ AmqpConnection >> initializeSocketConnection [ ] on: NetworkError do: [ :error | - socketConnectionStatus := FailedSocketConnection dueTo: error. + socketConnectionStatus := ClosedSocketConnection dueTo: error. LogRecord emitInfo: ( 'AMQP connection to <1s>:<2s> failed to establish because <3s>' expandMacrosWith: hostname with: portNumber printString diff --git a/source/Ansible-Protocol-Core/HealthyClosedSocketConnection.class.st b/source/Ansible-Protocol-Core/ClosedSocketConnection.class.st similarity index 59% rename from source/Ansible-Protocol-Core/HealthyClosedSocketConnection.class.st rename to source/Ansible-Protocol-Core/ClosedSocketConnection.class.st index db8db660..e5dcd8af 100644 --- a/source/Ansible-Protocol-Core/HealthyClosedSocketConnection.class.st +++ b/source/Ansible-Protocol-Core/ClosedSocketConnection.class.st @@ -1,5 +1,5 @@ Class { - #name : 'HealthyClosedSocketConnection', + #name : 'ClosedSocketConnection', #superclass : 'SocketConnectionStatus', #instVars : [ 'error' @@ -9,19 +9,19 @@ Class { } { #category : 'instance creation' } -HealthyClosedSocketConnection class >> dueTo: anError [ +ClosedSocketConnection class >> dueTo: anError [ ^ self new initializeDueTo: anError ] { #category : 'initialization' } -HealthyClosedSocketConnection >> initializeDueTo: anError [ +ClosedSocketConnection >> initializeDueTo: anError [ error := anError ] { #category : 'initialization' } -HealthyClosedSocketConnection >> whenConnected: aBlock whenNot: anotherBlock [ +ClosedSocketConnection >> whenConnected: aBlock whenNot: anotherBlock [ ^ anotherBlock cull: error ] diff --git a/source/Ansible-Protocol-Core/FailedSocketConnection.class.st b/source/Ansible-Protocol-Core/FailedSocketConnection.class.st deleted file mode 100644 index e6776a14..00000000 --- a/source/Ansible-Protocol-Core/FailedSocketConnection.class.st +++ /dev/null @@ -1,27 +0,0 @@ -Class { - #name : 'FailedSocketConnection', - #superclass : 'SocketConnectionStatus', - #instVars : [ - 'error' - ], - #category : 'Ansible-Protocol-Core', - #package : 'Ansible-Protocol-Core' -} - -{ #category : 'instance creation' } -FailedSocketConnection class >> dueTo: anError [ - - ^ self new initializeDueTo: anError -] - -{ #category : 'initialization' } -FailedSocketConnection >> initializeDueTo: anError [ - - error := anError -] - -{ #category : 'evaluating' } -FailedSocketConnection >> whenConnected: aBlock whenNot: anotherBlock [ - - ^anotherBlock cull: error -] From b7f1c5025c07b55411527e3b855383ef21b0c40a Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 14:19:05 -0300 Subject: [PATCH 53/65] Improve the reconnection logic when the failure is in the client's socket --- .../AmqpConnection.class.st | 20 +-- .../RabbitMQClientTest.class.st | 138 ++++++++++++------ .../RabbitMQPublisher.class.st | 19 +-- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 49 ++++--- 4 files changed, 128 insertions(+), 98 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index dcc288a2..3e3634f9 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -127,8 +127,10 @@ AmqpConnection >> ensureChannel: channelNumber [ { #category : 'connection-handling' } AmqpConnection >> ensureOpen [ - ( socket isConnected and: [ socket isOtherEndClosed not ] ) ifFalse: [ - AmqpDisconnectedError signal: 'Connection closed' ] + ( socket isConnected and: [ socket isOtherEndClosed not ] ) ifFalse: [ + AmqpDisconnectedError signal: 'Connection closed' ]. + + ^ true ] { #category : 'connection-handling' } @@ -313,14 +315,6 @@ AmqpConnection >> printOn: aStream [ aStream nextPutAll: ( 'AMPQ Connection closed due to <1s>' expandMacrosWith: reason ) ] ] -{ #category : 'connection-handling' } -AmqpConnection >> processAsyncEvents [ - - [ self ensureOpen. - codec stream isDataAvailable or: [ socket isConnected not ] - ] whileTrue: [ self mainLoopCycle ] -] - { #category : 'connection-handling' } AmqpConnection >> protocolClass [ @@ -483,11 +477,7 @@ AmqpConnection >> virtualHost [ { #category : 'connection-handling' } AmqpConnection >> waitForEvent [ - self ensureOpen. - [ codec stream peek ] - on: ConnectionTimedOut - do: [ ]. - ^ self processAsyncEvents + [ self ensureOpen ] whileTrue: [ self mainLoopCycle ] ] { #category : 'connection-handling' } diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index f24ebe4c..0df9b0a3 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -95,17 +95,7 @@ RabbitMQClientTest >> assertQueueStatusAfterPublishing: messagesToSend on: queue ] { #category : 'private - support' } -RabbitMQClientTest >> closeAllConnectionsOf: aRabbitmqContainerId for: aUsername because: aCloseReason [ - - ^ OSPlatform current runCommand: - ( 'docker exec <1s> rabbitmqctl close_all_user_connections <2s> <3s>' - expandMacrosWith: aRabbitmqContainerId - with: aUsername - with: aCloseReason ) -] - -{ #category : 'private - support' } -RabbitMQClientTest >> closeAllUserConnections [ +RabbitMQClientTest >> closeAllConnectionsFromTheBrokerSide [ | rabbitmqContainerId closeReason | @@ -123,6 +113,24 @@ RabbitMQClientTest >> closeAllUserConnections [ ] ] +{ #category : 'tests' } +RabbitMQClientTest >> closeAllConnectionsFromTheClientSide [ + + Socket allInstances + select: [ :socket | socket isConnected and: [ socket remotePort = 5672 ] ] + thenDo: #closeAndDestroy +] + +{ #category : 'private - support' } +RabbitMQClientTest >> closeAllConnectionsOf: aRabbitmqContainerId for: aUsername because: aCloseReason [ + + ^ OSPlatform current runCommand: + ( 'docker exec <1s> rabbitmqctl close_all_user_connections <2s> <3s>' + expandMacrosWith: aRabbitmqContainerId + with: aUsername + with: aCloseReason ) +] + { #category : 'private - accessing' } RabbitMQClientTest >> defaultRabbitMQPublisher [ @@ -144,14 +152,6 @@ RabbitMQClientTest >> defaultRabbitMQWorkerUsername [ ^ AmqpConnectionBuilder usingAMQP091Protocol credentials username ] -{ #category : 'private - support' } -RabbitMQClientTest >> largerWait [ - - "This delay aims to replicate the time required to successfully close all the RabbitMQ connections." - - ( Delay forSeconds: 120 ) wait -] - { #category : 'private - accessing' } RabbitMQClientTest >> queueName [ @@ -317,40 +317,74 @@ RabbitMQClientTest >> testPublisherConfirmationWhenMessageProcessed [ ] { #category : 'tests' } -RabbitMQClientTest >> testPublishingMessageWhenConnectionIsTemporallyLost [ - - self resumeWorkerDuring: [ - publisher - publishOnly: 'Hello' onQueueNamed: self queueName; - publishOnly: 'World' onQueueNamed: self queueName. +RabbitMQClientTest >> testPublishingMessageWhenClientUnexpectedlyClosesConnection [ + + self resumeWorkerDuring: [ + publisher + publishOnly: 'Hello' onQueueNamed: self queueName; + publishOnly: 'World' onQueueNamed: self queueName. + + self wait. + + self + assert: reversedTexts size equals: 2; + assert: reversedTexts first equals: 'olleH'; + assert: reversedTexts last equals: 'dlroW'. + + self + runMemoryLoggerDuring: [ + self + closeAllConnectionsFromTheClientSide; + waitUntilAllRabbitMQConnectionsClose. + publisher publishOnly: 'Test connection restored' onQueueNamed: self queueName + ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to Connection close while waiting for data.' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[ERROR\] AMQP Heartbeat failed unexpectedly \(connection closed while sending data\).' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to connection closed while sending data' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } + ]. - self wait. + self + assert: reversedTexts size equals: 3; + assert: reversedTexts last equals: 'derotser noitcennoc tseT' +] - self - assert: reversedTexts size equals: 2; - assert: reversedTexts first equals: 'olleH'; - assert: reversedTexts last equals: 'dlroW'. +{ #category : 'tests' } +RabbitMQClientTest >> testPublishingMessageWhenConnectionIsTemporallyLost [ - self - runMemoryLoggerDuring: [ - self - closeAllUserConnections; - largerWait. - publisher publishOnly: 'Test connection restored' onQueueNamed: self queueName - ] - assertingLogRecordsMatchRegexes: - { - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to CONNECTION_FORCED - CloseConnectionsTest' . - '\[ERROR\] RabbitMQClient disconnected due to Connection closed' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to (connection closed while sending data|send data timeout; data not sent)' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } - ]. + self resumeWorkerDuring: [ + publisher + publishOnly: 'Hello' onQueueNamed: self queueName; + publishOnly: 'World' onQueueNamed: self queueName. + + self wait. + + self + assert: reversedTexts size equals: 2; + assert: reversedTexts first equals: 'olleH'; + assert: reversedTexts last equals: 'dlroW'. + + self + runMemoryLoggerDuring: [ + self + closeAllConnectionsFromTheBrokerSide; + waitUntilAllRabbitMQConnectionsClose. + publisher publishOnly: 'Test connection restored' onQueueNamed: self queueName + ] + assertingLogRecordsMatchRegexes: + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to CONNECTION_FORCED - CloseConnectionsTest' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to (connection closed while sending data|send data timeout; data not sent)' . + '\[ERROR\] Attempt #1\/3 to connect to RabbitMQ failed\: (connection closed while sending data|send data timeout; data not sent)' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } + ]. - self - assert: reversedTexts size equals: 3; - assert: reversedTexts last equals: 'derotser noitcennoc tseT' + self + assert: reversedTexts size equals: 3; + assert: reversedTexts last equals: 'derotser noitcennoc tseT' ] { #category : 'tests' } @@ -379,3 +413,11 @@ RabbitMQClientTest >> wait [ ( Delay forMilliseconds: 200 ) wait ] + +{ #category : 'private - support' } +RabbitMQClientTest >> waitUntilAllRabbitMQConnectionsClose [ + + "This delay aims to replicate the time required to successfully close all the RabbitMQ connections." + + ( Delay forSeconds: 120 ) wait +] diff --git a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st index 046cb3f6..0a88dd35 100644 --- a/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQPublisher.class.st @@ -77,23 +77,20 @@ RabbitMQPublisher >> publish: aMessageCollection onQueueNamed: aQueueName [ RabbitMQPublisher >> publishOnly: aMessage onQueueNamed: aQueueName [ | tryToPublishMessage | - self ensureChannelOpen. tryToPublishMessage := [ + self ensureChannelOpen. channel basicPublish: aMessage utf8Encoded exchange: '' routingKey: aQueueName - properties: self persistentDeliveryMode + properties: self persistentDeliveryMode. + self logDebuggingInfoFor: aMessage publishedTo: aQueueName ]. - self logDebuggingInfoFor: aMessage publishedTo: aQueueName. - - tryToPublishMessage - on: self connectivityErrors - do: [ :signal | - connection hardCloseDescribedWith: signal messageText. - self ensureChannelOpen. - signal return: tryToPublishMessage value - ] + + self try: tryToPublishMessage onConnectivityErrorDo: [ :attemptNumber :error | + connection hardCloseDescribedWith: error messageText. + self logFailedConnectionAttempt: attemptNumber dueTo: error + ] ] { #category : 'connecting' } diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index ffa457a1..41a30915 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -47,13 +47,6 @@ RabbitMQWorker >> declareQueueInChannel [ channel queueDeclare: self queueName durable: ( options at: #queueDurable ifAbsent: [ true ] ) ] -{ #category : 'private' } -RabbitMQWorker >> ensureChannelOpen [ - - super ensureChannelOpen. - self declareQueueInChannel -] - { #category : 'private' } RabbitMQWorker >> initializeConfiguredBy: anOptionsDictionary processingMessagesWith: aMessageProcessor [ @@ -76,30 +69,22 @@ RabbitMQWorker >> logDebuggingInfoFor: aMessage [ ] ] -{ #category : 'private - logging' } -RabbitMQWorker >> logDisconnectionDueTo: anError [ - - LogRecord emitError: - ( 'RabbitMQClient disconnected due to <1s>' expandMacrosWith: anError messageText ) -] - { #category : 'accessing' } RabbitMQWorker >> queueName [ ^ options at: #queueName ] -{ #category : 'controlling' } -RabbitMQWorker >> start [ +{ #category : 'private' } +RabbitMQWorker >> restartConsumingFromQueue [ - super start. - self startProcessing + self + ensureChannelOpen; + setUpChannelConsumingCallback ] { #category : 'private' } -RabbitMQWorker >> startConsumingFromQueue [ - - self ensureChannelOpen. +RabbitMQWorker >> setUpChannelConsumingCallback [ channel prefetchCount: 1. channel consumeFrom: self queueName applying: [ :message | @@ -109,6 +94,22 @@ RabbitMQWorker >> startConsumingFromQueue [ ] ] +{ #category : 'controlling' } +RabbitMQWorker >> start [ + + super start. + self startProcessing +] + +{ #category : 'private' } +RabbitMQWorker >> startConsumingFromQueue [ + + self + ensureChannelOpen; + declareQueueInChannel; + setUpChannelConsumingCallback +] + { #category : 'private' } RabbitMQWorker >> startProcessing [ @@ -118,9 +119,9 @@ RabbitMQWorker >> startProcessing [ [ connection waitForEvent ] on: self connectivityErrors do: [ :error | - self - logDisconnectionDueTo: error; - startConsumingFromQueue + connection hardCloseDescribedWith: error messageText. + self restartConsumingFromQueue. + error return ] ] repeat ] From a699fd15fe0304ff48fa23ded03d55eb1ad17ceb Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 15:43:04 -0300 Subject: [PATCH 54/65] Add SocketError as a possible connectivity error --- source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st | 2 +- source/Ansible-RabbitMQ/RabbitMQClient.class.st | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index a041d846..79b0505d 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -19,7 +19,7 @@ AmqpHeartbeatSender class >> keepingOpen: anAmqpConnection [ { #category : 'accessing' } AmqpHeartbeatSender >> connectivityErrors [ - ^ NetworkError , AmqpDisconnectedError + ^ NetworkError , AmqpDisconnectedError , SocketError ] { #category : 'accessing' } diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index e098419d..5f0b4e4c 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -32,7 +32,7 @@ RabbitMQClient >> channel [ { #category : 'private - configuring' } RabbitMQClient >> connectivityErrors [ - ^ NetworkError , AmqpDisconnectedError + ^ NetworkError , AmqpDisconnectedError , SocketError ] { #category : 'initialization' } From 45b9a6495841e36210bc9ec50d70b80e94ecf6af Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 15:47:35 -0300 Subject: [PATCH 55/65] Add PrimitiveFailed as a possible connectivity error for compatibility with pharo older versions --- source/Ansible-Protocol-Core/AmqpConnection.class.st | 2 +- source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st | 2 +- source/Ansible-RabbitMQ/RabbitMQClient.class.st | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 3e3634f9..33ec3926 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -52,7 +52,7 @@ AmqpConnection >> becomeCloseAfter: aBlock [ with: closingMethod replyText ). heartbeatSender stop. [ socket close ] - on: SocketError + on: PrimitiveFailed, SocketError do: [ :error | " If the socket was unexpectedly closed, trying to close it again will raise a SocketError that we want to silence " diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index 79b0505d..83c824ff 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -19,7 +19,7 @@ AmqpHeartbeatSender class >> keepingOpen: anAmqpConnection [ { #category : 'accessing' } AmqpHeartbeatSender >> connectivityErrors [ - ^ NetworkError , AmqpDisconnectedError , SocketError + ^ NetworkError , AmqpDisconnectedError , SocketError , PrimitiveFailed ] { #category : 'accessing' } diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 5f0b4e4c..7319a962 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -32,7 +32,7 @@ RabbitMQClient >> channel [ { #category : 'private - configuring' } RabbitMQClient >> connectivityErrors [ - ^ NetworkError , AmqpDisconnectedError , SocketError + ^ NetworkError , AmqpDisconnectedError , SocketError , PrimitiveFailed ] { #category : 'initialization' } From be39a5d40c693f1b1164539b0f086d01e86560fa Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 15:56:28 -0300 Subject: [PATCH 56/65] Improve AmqpConnection description --- .../Ansible-Protocol-Core/AmqpConnection.class.st | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 33ec3926..e131de68 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -95,10 +95,10 @@ AmqpConnection >> codec [ { #category : 'accessing' } AmqpConnection >> connectionPairsDescription [ - ^ 'localhost:<1p>-><2s>:<3p>' - expandMacrosWith: ([socket port] on: SocketError do: [ :error | error return: 0 ]) - with: hostname - with: portNumber + ^ 'localhost:<1p>-><2s>:<3p>' + expandMacrosWith: self localPortDescription + with: hostname + with: portNumber ] { #category : 'connection-handling' } @@ -220,6 +220,12 @@ AmqpConnection >> installChannel0 [ channel mapEvent: self protocolClass connectionCloseMethod to: self selector: #handleConnectionClose: ] +{ #category : 'accessing' } +AmqpConnection >> localPortDescription [ + + ^ socket isConnected then: [ socket localPort ] otherwise: [ 0 ] +] + { #category : 'connection-handling' } AmqpConnection >> mainLoopCycle [ From 0ef360d302780e38d05d534363d1c11721ec2f7b Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 16:45:58 -0300 Subject: [PATCH 57/65] Homogeneize the exchange and queue declaration using a builder Deprecate older messages --- .../AmqpChannel.extension.st | 97 ++++++++++++++++ .../AmqpChannel.class.st | 88 --------------- .../AmqpExchangeDeclareBuilder.class.st | 6 + .../AmqpQueueDeclareBuilder.class.st | 6 + .../RabbitMQClientTest.class.st | 6 +- .../Ansible-RabbitMQ/RabbitMQWorker.class.st | 11 +- source/Ansible-Tests/AMQPTest.class.st | 106 +++++++++--------- source/Ansible-Tests/package.st | 2 +- 8 files changed, 176 insertions(+), 146 deletions(-) diff --git a/source/Ansible-Deprecated-v3/AmqpChannel.extension.st b/source/Ansible-Deprecated-v3/AmqpChannel.extension.st index b31e3f7c..2463775c 100644 --- a/source/Ansible-Deprecated-v3/AmqpChannel.extension.st +++ b/source/Ansible-Deprecated-v3/AmqpChannel.extension.st @@ -11,3 +11,100 @@ AmqpChannel >> confirmMessagesPublicationWith: anAckBlock andThoseNotProcessedWi self onPublicationConfirmationDo: anAckBlock onRejectionDo: aNackBlock ] + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> exchangeDeclare: exchangeName type: typeString [ + + self deprecated: 'Use #declareExchangeNamed:of:applying: directly'. + + ^ self + exchangeDeclare: exchangeName + type: typeString + durable: false + autoDelete: false + passive: false + arguments: nil +] + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable [ + + self deprecated: 'Use #declareExchangeNamed:of:applying: directly'. + + ^ self + exchangeDeclare: exchangeName + type: typeString + durable: durable + autoDelete: false + passive: false + arguments: nil +] + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable autoDelete: autoDelete passive: passive [ + + self deprecated: 'Use #declareExchangeNamed:of:applying: directly'. + + ^ self + exchangeDeclare: exchangeName + type: typeString + durable: durable + autoDelete: autoDelete + passive: passive + arguments: nil +] + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable autoDelete: autoDelete passive: passive arguments: aDictionary [ + + self deprecated: 'Use #declareExchangeNamed:of:applying: directly'. + + ^ self declareExchangeNamed: exchangeName of: typeString applying: [ :builder | + passive then: [ builder bePassive ]. + durable then: [ builder beDurable ]. + autoDelete then: [ builder autoDelete ]. + builder useAsArguments: aDictionary + ] +] + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> queueDeclare: queueName [ + + self deprecated: 'Use #declareQueueApplying: directly'. + ^ self + queueDeclare: queueName + durable: false + exclusive: false + autoDelete: false + passive: false + arguments: nil +] + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> queueDeclare: queueName durable: durable [ + + self deprecated: 'Use #declareQueueApplying: directly'. + ^ self + queueDeclare: queueName + durable: durable + exclusive: false + autoDelete: false + passive: false + arguments: nil +] + +{ #category : '*Ansible-Deprecated-v3' } +AmqpChannel >> queueDeclare: queueName durable: durable exclusive: exclusive autoDelete: autoDelete passive: passive arguments: aDictionary [ + + self deprecated: 'Use #declareQueueApplying: directly'. + + ^ self declareQueueApplying: [ :builder | + builder + name: queueName; + useAsArguments: aDictionary. + passive then: [ builder bePassive ]. + durable then: [ builder beDurable ]. + exclusive then: [ builder beExclusive ]. + autoDelete then: [ builder autoDelete ] + ] +] diff --git a/source/Ansible-Protocol-Core/AmqpChannel.class.st b/source/Ansible-Protocol-Core/AmqpChannel.class.st index 029ecf61..374bdeea 100644 --- a/source/Ansible-Protocol-Core/AmqpChannel.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannel.class.st @@ -227,56 +227,6 @@ AmqpChannel >> declareQueueApplying: aBlock [ ^ self rpc: ( builder buildApplying: aBlock ) ] -{ #category : 'AMQP exchange management' } -AmqpChannel >> exchangeDeclare: exchangeName type: typeString [ - - ^ self - exchangeDeclare: exchangeName - type: typeString - durable: false - autoDelete: false - passive: false - arguments: nil -] - -{ #category : 'AMQP exchange management' } -AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable [ - - ^ self - exchangeDeclare: exchangeName - type: typeString - durable: durable - autoDelete: false - passive: false - arguments: nil -] - -{ #category : 'AMQP exchange management' } -AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable autoDelete: autoDelete passive: passive [ - - ^ self - exchangeDeclare: exchangeName - type: typeString - durable: durable - autoDelete: autoDelete - passive: passive - arguments: nil -] - -{ #category : 'AMQP exchange management' } -AmqpChannel >> exchangeDeclare: exchangeName type: typeString durable: durable autoDelete: autoDelete passive: passive arguments: aDictionary [ - - ^ self - rpc: - ( protocolVersion exchangeDeclareMethod new - exchange: exchangeName; - type: typeString; - passive: passive; - durable: durable; - autoDelete: autoDelete; - arguments: aDictionary ) -] - { #category : 'AMQP exchange management' } AmqpChannel >> exchangeDelete: exchangeName [ @@ -387,44 +337,6 @@ AmqpChannel >> queueBind: queue exchange: exchange routingKey: routingKey argume arguments: aDictionary ) ] -{ #category : 'AMQP queue management' } -AmqpChannel >> queueDeclare: queueName [ - - ^ self - queueDeclare: queueName - durable: false - exclusive: false - autoDelete: false - passive: false - arguments: nil -] - -{ #category : 'AMQP queue management' } -AmqpChannel >> queueDeclare: queueName durable: durable [ - - ^ self - queueDeclare: queueName - durable: durable - exclusive: false - autoDelete: false - passive: false - arguments: nil -] - -{ #category : 'AMQP queue management' } -AmqpChannel >> queueDeclare: queueName durable: durable exclusive: exclusive autoDelete: autoDelete passive: passive arguments: aDictionary [ - - ^ self - rpc: - ( protocolVersion queueDeclareMethod new - queue: queueName; - passive: passive; - durable: durable; - exclusive: exclusive; - autoDelete: autoDelete; - arguments: aDictionary ) -] - { #category : 'AMQP queue management' } AmqpChannel >> queueDelete: queueName [ diff --git a/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st b/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st index 5a04baf5..628eeec5 100644 --- a/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st +++ b/source/Ansible-Protocol-Core/AmqpExchangeDeclareBuilder.class.st @@ -55,3 +55,9 @@ AmqpExchangeDeclareBuilder >> initializeNamed: aName of: aType for: aProtocolVer passive := false. arguments := nil ] + +{ #category : 'building' } +AmqpExchangeDeclareBuilder >> useAsArguments: aDictionary [ + + arguments := aDictionary +] diff --git a/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st b/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st index 29d41392..a9b3a868 100644 --- a/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st +++ b/source/Ansible-Protocol-Core/AmqpQueueDeclareBuilder.class.st @@ -76,3 +76,9 @@ AmqpQueueDeclareBuilder >> name: aQueueName [ queueName := aQueueName ] + +{ #category : 'configuring' } +AmqpQueueDeclareBuilder >> useAsArguments: aDictionary [ + + arguments := aDictionary +] diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index 0df9b0a3..79c18d1b 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -62,7 +62,7 @@ RabbitMQClientTest >> assertQueueStatusAfterConsuming: messagesToSend from: queu ]. worker startConsumingFromQueue. [ - queue := worker channel queueDeclare: queueName. + queue := worker channel declareQueueApplying: [ :builder | builder name: queueName ]. self assert: messageWasConsumed; assert: queue method messageCount equals: 0; @@ -80,13 +80,13 @@ RabbitMQClientTest >> assertQueueStatusAfterPublishing: messagesToSend on: queue onPublicationConfirmationDo: [ :command | messageWasPublished := true ] onRejectionDo: [ :command | self fail ]. - queue := publisher channel queueDeclare: queueName. + queue := publisher channel declareQueueApplying: [ :builder | builder name: queueName ]. publisher publishOnly: messagesToSend onQueueNamed: queueName. self wait. - queue := publisher channel queueDeclare: queueName. + queue := publisher channel declareQueueApplying: [ :builder | builder name: queueName ]. self assert: queue method messageCount equals: 1; diff --git a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st index 41a30915..b08c2985 100644 --- a/source/Ansible-RabbitMQ/RabbitMQWorker.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQWorker.class.st @@ -44,7 +44,10 @@ RabbitMQWorker class >> configuredBy: aConfigurationAction processingPayloadWith { #category : 'private' } RabbitMQWorker >> declareQueueInChannel [ - channel queueDeclare: self queueName durable: ( options at: #queueDurable ifAbsent: [ true ] ) + channel declareQueueApplying: [ :builder | + builder name: self queueName. + self makeQueueDurable then: [ builder beDurable ] + ] ] { #category : 'private' } @@ -69,6 +72,12 @@ RabbitMQWorker >> logDebuggingInfoFor: aMessage [ ] ] +{ #category : 'private' } +RabbitMQWorker >> makeQueueDurable [ + + ^ options at: #queueDurable ifAbsent: [ true ] +] + { #category : 'accessing' } RabbitMQWorker >> queueName [ diff --git a/source/Ansible-Tests/AMQPTest.class.st b/source/Ansible-Tests/AMQPTest.class.st index 16fa9fde..33b12008 100644 --- a/source/Ansible-Tests/AMQPTest.class.st +++ b/source/Ansible-Tests/AMQPTest.class.st @@ -1,10 +1,11 @@ Class { - #name : #AMQPTest, - #superclass : #TestCase, - #category : #'Ansible-Tests' + #name : 'AMQPTest', + #superclass : 'TestCase', + #category : 'Ansible-Tests', + #package : 'Ansible-Tests' } -{ #category : #'tests - asserting' } +{ #category : 'tests - asserting' } AMQPTest >> assert: aMessageCollection has: anAmount messagesAndAreEqualsTo: anOrderedCollection [ self @@ -13,7 +14,7 @@ AMQPTest >> assert: aMessageCollection has: anAmount messagesAndAreEqualsTo: anO hasSameElements: anOrderedCollection ] -{ #category : #'tests - asserting' } +{ #category : 'tests - asserting' } AMQPTest >> assertContentsFor: messageReceived [ self @@ -24,20 +25,20 @@ AMQPTest >> assertContentsFor: messageReceived [ assert: messageReceived method messageCount equals: 0 ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> assertQueueNamed: queueName IsEmtpyOn: channel [ - | queue | + | queue | - queue := channel queueDeclare: queueName. + queue := channel declareQueueApplying: [ :builder | builder name: queueName ]. - self - assert: queue method messageCount equals: 0; - assert: queue method consumerCount equals: 0. - ^ queue + self + assert: queue method messageCount equals: 0; + assert: queue method consumerCount equals: 0. + ^ queue ] -{ #category : #'tests - accessing' } +{ #category : 'tests - accessing' } AMQPTest >> messageAs: aLevelDescription from: aSourceIdentifier to: aTargetIdentifier [ ^ (WriteStream on: String new) @@ -47,7 +48,7 @@ AMQPTest >> messageAs: aLevelDescription from: aSourceIdentifier to: aTargetIden contents ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> publish: aMessageCollection onExchangeNamed: anExchangeName of: anExchangeType [ self @@ -57,7 +58,7 @@ AMQPTest >> publish: aMessageCollection onExchangeNamed: anExchangeName of: anEx of: anExchangeType ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> publish: aMessageCollection onQueueNamed: aQueueName [ self @@ -65,7 +66,7 @@ AMQPTest >> publish: aMessageCollection onQueueNamed: aQueueName [ | channel queue | channel := connection createChannel. - channel queueDeclare: aQueueName. + channel declareQueueApplying: [ :builder | builder name: aQueueName ]. aMessageCollection do: [ :message | @@ -77,7 +78,7 @@ AMQPTest >> publish: aMessageCollection onQueueNamed: aQueueName [ ]. ( Delay forMilliseconds: 100 ) wait. - queue := channel queueDeclare: aQueueName. + queue := channel declareQueueApplying: [ :builder | builder name: aQueueName ]. self assert: queue method messageCount equals: aMessageCollection size; @@ -85,7 +86,7 @@ AMQPTest >> publish: aMessageCollection onQueueNamed: aQueueName [ ] ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> publish: aMessageCollection to: aRoute onExchangeNamed: anExchangeName of: anExchangeType [ self @@ -99,7 +100,7 @@ AMQPTest >> publish: aMessageCollection to: aRoute onExchangeNamed: anExchangeNa withProperties: connection protocolClass basicPropertiesClass new ] ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> publish: aMessageCollection to: aRoute onExchangeNamed: anExchangeName @@ -129,7 +130,7 @@ AMQPTest >> publish: aMessageCollection assert: queue method consumerCount equals: 0 "The Queue will always be empty as it does not maintain old messages." ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> publish: aMessageCollection with: aHeadersDictionary onHeadersExchangeNamed: anExchangeName [ self @@ -143,7 +144,7 @@ AMQPTest >> publish: aMessageCollection with: aHeadersDictionary onHeadersExchan withProperties: ( connection protocolClass basicPropertiesClass new headers: aHeadersDictionary )] ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> setUp [ "Clean up the queues in case any of the previous tests failed." @@ -157,7 +158,7 @@ AMQPTest >> setUp [ ] ] -{ #category : #'tests - accessing' } +{ #category : 'tests - accessing' } AMQPTest >> sourceIdentifiedBy: source and: target [ ^ (WriteStream on: String new) @@ -167,7 +168,7 @@ AMQPTest >> sourceIdentifiedBy: source and: target [ contents ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> spawnWorkerNamed: aProcessName boundToHeadersExchangeNamed: anExchangeName matchingToAllOf: aHeadersDictionary @@ -182,7 +183,7 @@ AMQPTest >> spawnWorkerNamed: aProcessName applying: aBlock ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> spawnWorkerNamed: aProcessName boundToHeadersExchangeNamed: anExchangeName matchingToAnyOf: aHeadersDictionary @@ -197,7 +198,7 @@ AMQPTest >> spawnWorkerNamed: aProcessName applying: aBlock ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> spawnWorkerNamed: aProcessName consumingFrom: aQueueName applying: aBlock [ self @@ -206,7 +207,7 @@ AMQPTest >> spawnWorkerNamed: aProcessName consumingFrom: aQueueName applying: a channel := connection createChannel. channel - queueDeclare: aQueueName; + declareQueueApplying: [ :builder | builder name: aQueueName ]; prefetchCount: 1; consumeFrom: 'tasks' applying: [ :messageReceived | @@ -231,7 +232,7 @@ AMQPTest >> spawnWorkerNamed: aProcessName consumingFrom: aQueueName applying: a ] ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> spawnWorkerNamed: aProcessName consumingFromAll: aRouteSet boundToExchangeNamed: anExchangeName @@ -247,7 +248,7 @@ AMQPTest >> spawnWorkerNamed: aProcessName applying: aBlock ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> spawnWorkerNamed: aProcessName consumingFromAll: aRouteSet boundToExchangeNamed: anExchangeName @@ -294,7 +295,7 @@ AMQPTest >> spawnWorkerNamed: aProcessName ] ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> spawnWorkerNamed: aWorkerName consumingFromExchangeNamed: anExchangeName of: anExchangeType applying: aBlock [ ^ self @@ -305,7 +306,7 @@ AMQPTest >> spawnWorkerNamed: aWorkerName consumingFromExchangeNamed: anExchange applying: aBlock ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testBasicConsume [ | channel queue | @@ -315,7 +316,7 @@ AMQPTest >> testBasicConsume [ self withLocalhostConnectionDo: [ :connection | channel := connection createChannel. - queue := channel queueDeclare: 'tasks'. + queue := channel declareQueueApplying: [ :builder | builder name: 'tasks' ]. channel prefetchCount: 1. @@ -331,7 +332,7 @@ AMQPTest >> testBasicConsume [ channel basicAck: messageReceived method deliveryTag ]. - queue := channel queueDeclare: 'tasks'. + queue := channel declareQueueApplying: [ :builder | builder name: 'tasks' ]. self assert: queue method messageCount equals: 0; @@ -341,7 +342,7 @@ AMQPTest >> testBasicConsume [ ] ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testBasicConsumeWithMultipleWorkers [ | firstWorkerMessages secondWorkerMessages firstWorker secondWorker | @@ -383,7 +384,7 @@ AMQPTest >> testBasicConsumeWithMultipleWorkers [ assert: firstWorkerMessages size + secondWorkerMessages size equals: 6 ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testBasicGet [ self @@ -395,7 +396,7 @@ AMQPTest >> testBasicGet [ ] ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testBasicGetWithExplicitAcknowledge [ self @@ -415,7 +416,7 @@ AMQPTest >> testBasicGetWithExplicitAcknowledge [ ] ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testBasicGetWithImplicitAcknowledge [ self @@ -433,7 +434,7 @@ AMQPTest >> testBasicGetWithImplicitAcknowledge [ ] ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testPublisherSubscriberUsingFanoutExchange [ | firstWorkerMessages secondWorkerMessages firstWorker secondWorker | @@ -468,7 +469,7 @@ AMQPTest >> testPublisherSubscriberUsingFanoutExchange [ assert: secondWorkerMessages size equals: 5 ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testPublisherSubscriberUsingHeadersExchangeMatchingAllProperties [ | exchangeName sourceAtarget1InfoMessages sourceBInfoMessages errorMessages loggers | @@ -548,7 +549,7 @@ AMQPTest >> testPublisherSubscriberUsingHeadersExchangeMatchingAllProperties [ with: (self messageAs: 'error' from: $B to: $2)) ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testPublisherSubscriberUsingHeadersExchangeMatchingAnyProperty [ | exchangeName sourceAtarget1InfoMessages sourceAtarget1ErrorMessages sourceAtarget2AllMessages sourceBtarget1AllMessages loggers | @@ -642,7 +643,7 @@ AMQPTest >> testPublisherSubscriberUsingHeadersExchangeMatchingAnyProperty [ with: (self messageAs: 'error' from: $B to: $1)) ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testPublisherSubscriberUsingRoutesBoundToDirectExchange [ | errorLoggerMessages loggerMessages errorLogger logger | @@ -698,7 +699,7 @@ AMQPTest >> testPublisherSubscriberUsingRoutesBoundToDirectExchange [ assert: loggerMessages size equals: 9 ] -{ #category : #tests } +{ #category : 'tests' } AMQPTest >> testPublisherSubscriberUsingTopicsExchange [ | publicErrorLoggerMessages anonymousInfoLoggerMessages publicErrorLogger anonymousInfoLogger | @@ -756,7 +757,7 @@ AMQPTest >> testPublisherSubscriberUsingTopicsExchange [ assert: anonymousInfoLoggerMessages size equals: 6 ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> withLocalhostConnectionDo: block [ | connection | @@ -771,22 +772,21 @@ AMQPTest >> withLocalhostConnectionDo: block [ whenNot: [ :error | self fail: error messageText ] ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> withQueueNamed: aQueueName declaredOnChannelDo: aBlock [ self withQueueNamed: aQueueName declaredOnChannelDo: aBlock deleteQueueOnReturn: true ] -{ #category : #'tests - support' } +{ #category : 'tests - support' } AMQPTest >> withQueueNamed: aQueueName declaredOnChannelDo: aBlock deleteQueueOnReturn: aBoolean [ - self - withLocalhostConnectionDo: [ :connection | - | channel | - - channel := connection createChannel. - aBlock value: ( channel queueDeclare: aQueueName ) value: channel. - aBoolean - ifTrue: [ channel queueDelete: aQueueName ] - ] + self withLocalhostConnectionDo: [ :connection | + | channel | + channel := connection createChannel. + aBlock + value: ( channel declareQueueApplying: [ :builder | builder name: aQueueName ] ) + value: channel. + aBoolean ifTrue: [ channel queueDelete: aQueueName ] + ] ] diff --git a/source/Ansible-Tests/package.st b/source/Ansible-Tests/package.st index 99be9446..df8ef7b1 100644 --- a/source/Ansible-Tests/package.st +++ b/source/Ansible-Tests/package.st @@ -1 +1 @@ -Package { #name : #'Ansible-Tests' } +Package { #name : 'Ansible-Tests' } From 6239fec744f28d670c37c4cba370260226a9d4e1 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 19:45:14 -0300 Subject: [PATCH 58/65] Create a global to wrap the socket error across different pharo versions --- .../AmqpConnection.class.st | 2 +- .../AmqpHeartbeatSender.class.st | 2 +- .../Ansible-RabbitMQ/RabbitMQClient.class.st | 2 +- .../BaselineOfAnsible.class.st | 40 +++++++++++++------ 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index e131de68..22394a8a 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -52,7 +52,7 @@ AmqpConnection >> becomeCloseAfter: aBlock [ with: closingMethod replyText ). heartbeatSender stop. [ socket close ] - on: PrimitiveFailed, SocketError + on: ExpectedSocketFailure do: [ :error | " If the socket was unexpectedly closed, trying to close it again will raise a SocketError that we want to silence " diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index 83c824ff..c4777576 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -19,7 +19,7 @@ AmqpHeartbeatSender class >> keepingOpen: anAmqpConnection [ { #category : 'accessing' } AmqpHeartbeatSender >> connectivityErrors [ - ^ NetworkError , AmqpDisconnectedError , SocketError , PrimitiveFailed + ^ NetworkError , AmqpDisconnectedError , ExpectedSocketFailure ] { #category : 'accessing' } diff --git a/source/Ansible-RabbitMQ/RabbitMQClient.class.st b/source/Ansible-RabbitMQ/RabbitMQClient.class.st index 7319a962..337b0b8c 100644 --- a/source/Ansible-RabbitMQ/RabbitMQClient.class.st +++ b/source/Ansible-RabbitMQ/RabbitMQClient.class.st @@ -32,7 +32,7 @@ RabbitMQClient >> channel [ { #category : 'private - configuring' } RabbitMQClient >> connectivityErrors [ - ^ NetworkError , AmqpDisconnectedError , SocketError , PrimitiveFailed + ^ NetworkError , AmqpDisconnectedError , ExpectedSocketFailure ] { #category : 'initialization' } diff --git a/source/BaselineOfAnsible/BaselineOfAnsible.class.st b/source/BaselineOfAnsible/BaselineOfAnsible.class.st index 60b33b1a..f33af843 100644 --- a/source/BaselineOfAnsible/BaselineOfAnsible.class.st +++ b/source/BaselineOfAnsible/BaselineOfAnsible.class.st @@ -8,18 +8,34 @@ Class { { #category : 'baselines' } BaselineOfAnsible >> baseline: spec [ - - spec for: #pharo do: [ - self - setUpDependencies: spec; - setUpDeploymentPackages: spec; - setUpTestPackages: spec; - setUpToolsPackages: spec. - spec - group: 'Deployment' with: #( 'Deployment-08' 'Deployment-091' 'RabbitMQ' ); - group: 'CI' with: #( 'Tests' ); - group: 'Development' with: #( 'Tests' 'Tools' ) - ] + + spec for: #pharo do: [ + self + setUpDependencies: spec; + setUpDeploymentPackages: spec; + setUpTestPackages: spec; + setUpToolsPackages: spec. + spec + group: 'Deployment' with: #( 'Deployment-08' 'Deployment-091' 'RabbitMQ' ); + group: 'CI' with: #( 'Tests' ); + group: 'Development' with: #( 'Tests' 'Tools' ). + spec postLoadDoIt: #postLoadInitialization + ] +] + +{ #category : 'initialization' } +BaselineOfAnsible >> postLoadInitialization [ + + " ExpectedSocketFailure is used to handle socket failures across + different Pharo versions (SocketError was introduced in Pharo 11) " + + Smalltalk + at: #ExpectedSocketFailure + put: + ( Smalltalk + at: #SocketError + ifPresent: [ :socketError | socketError ] + ifAbsent: [ PrimitiveFailed ] ) ] { #category : 'accessing' } From 350ae76b98e3e35796d8ef5ba9e25b32c7fdc213 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 22:40:02 -0300 Subject: [PATCH 59/65] Use Smalltalk>>#includesKey: to make it portable to Pharo8 --- source/BaselineOfAnsible/BaselineOfAnsible.class.st | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/source/BaselineOfAnsible/BaselineOfAnsible.class.st b/source/BaselineOfAnsible/BaselineOfAnsible.class.st index f33af843..ff27f456 100644 --- a/source/BaselineOfAnsible/BaselineOfAnsible.class.st +++ b/source/BaselineOfAnsible/BaselineOfAnsible.class.st @@ -25,17 +25,12 @@ BaselineOfAnsible >> baseline: spec [ { #category : 'initialization' } BaselineOfAnsible >> postLoadInitialization [ - " ExpectedSocketFailure is used to handle socket failures across different Pharo versions (SocketError was introduced in Pharo 11) " - Smalltalk - at: #ExpectedSocketFailure - put: - ( Smalltalk - at: #SocketError - ifPresent: [ :socketError | socketError ] - ifAbsent: [ PrimitiveFailed ] ) + Smalltalk at: #ExpectedSocketFailure put: ( ( Smalltalk includesKey: #SocketError ) + then: [ Smalltalk at: #SocketError ] + otherwise: [ PrimitiveFailed ] ) ] { #category : 'accessing' } From c123dc64e7bce2c792266e9058c630b8dc4d2f54 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Tue, 27 Aug 2024 23:22:48 -0300 Subject: [PATCH 60/65] Avoid Heartbeat process termination infinite loop in some Pharo versions --- source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st index c4777576..0527e165 100644 --- a/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st +++ b/source/Ansible-Protocol-Core/AmqpHeartbeatSender.class.st @@ -83,7 +83,7 @@ AmqpHeartbeatSender >> startBeatingEvery: aTimeInSeconds [ AmqpHeartbeatSender >> stop [ isStarted then: [ - process ifNotNil: #terminate. - isStarted := false + isStarted := false. + process ifNotNil: #terminate ] ] From 073c3b0ea7c4b9c2d630b7bce8651a3863596c74 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Wed, 28 Aug 2024 12:55:53 -0300 Subject: [PATCH 61/65] Use ifTrue:ifFalse: in postLoadInitialization At the moment of executing the code, that extension is not yet loaded --- source/BaselineOfAnsible/BaselineOfAnsible.class.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/BaselineOfAnsible/BaselineOfAnsible.class.st b/source/BaselineOfAnsible/BaselineOfAnsible.class.st index ff27f456..101e4384 100644 --- a/source/BaselineOfAnsible/BaselineOfAnsible.class.st +++ b/source/BaselineOfAnsible/BaselineOfAnsible.class.st @@ -29,8 +29,8 @@ BaselineOfAnsible >> postLoadInitialization [ different Pharo versions (SocketError was introduced in Pharo 11) " Smalltalk at: #ExpectedSocketFailure put: ( ( Smalltalk includesKey: #SocketError ) - then: [ Smalltalk at: #SocketError ] - otherwise: [ PrimitiveFailed ] ) + ifTrue: [ Smalltalk at: #SocketError ] + ifFalse: [ PrimitiveFailed ] ) ] { #category : 'accessing' } From 8efb90111eecdff11ec0c7a76b93eb4276165f03 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Thu, 29 Aug 2024 18:11:45 -0300 Subject: [PATCH 62/65] Release waiting processes on socket when that one is destroyed This fixes the issue https://github.com/pharo-project/pharo/issues/15975 presented in older version of pharo --- .../Semaphore.extension.st | 10 ++ .../Socket.extension.st | 145 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 source/Ansible-Pharo-Pending-Patches/Semaphore.extension.st create mode 100644 source/Ansible-Pharo-Pending-Patches/Socket.extension.st diff --git a/source/Ansible-Pharo-Pending-Patches/Semaphore.extension.st b/source/Ansible-Pharo-Pending-Patches/Semaphore.extension.st new file mode 100644 index 00000000..19452c76 --- /dev/null +++ b/source/Ansible-Pharo-Pending-Patches/Semaphore.extension.st @@ -0,0 +1,10 @@ +Extension { #name : 'Semaphore' } + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Semaphore >> waitTimeoutMilliseconds: anInteger [ + "Wait on this semaphore for up to the given number of milliseconds, then timeout. + Return true if the deadline expired, false otherwise." + | d | + d := DelayWaitTimeout new setDelay: (anInteger max: 0) forSemaphore: self. + ^d wait +] diff --git a/source/Ansible-Pharo-Pending-Patches/Socket.extension.st b/source/Ansible-Pharo-Pending-Patches/Socket.extension.st new file mode 100644 index 00000000..423092af --- /dev/null +++ b/source/Ansible-Pharo-Pending-Patches/Socket.extension.st @@ -0,0 +1,145 @@ +Extension { #name : 'Socket' } + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> connectTo: hostAddress port: port waitForConnectionFor: timeout [ + "Initiate a connection to the given port at the given host + address. Waits until the connection is established or time outs." + + self connectNonBlockingTo: hostAddress port: port. + self + waitForConnectionFor: timeout + ifClosed: [ + ConnectionClosed signal: 'Connection aborted to ' + , (NetNameResolver stringFromAddress: hostAddress) , ':' + , port asString ] + ifTimedOut: [ + ConnectionTimedOut signal: 'Cannot connect to ' + , (NetNameResolver stringFromAddress: hostAddress) , ':' + , port asString ] +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> destroy [ + "Destroy this socket. Its connection, if any, is aborted and its resources are freed. + Any processes waiting on the socket are freed immediately, but it is up to them to + recognize that the socket has been destroyed. + Do nothing if the socket has already been destroyed (i.e., if its socketHandle is nil)." + + socketHandle ifNotNil: [ + | saveSemaphores | + self isValid ifTrue: [ self primSocketDestroy: socketHandle ]. + socketHandle := nil. + Smalltalk unregisterExternalObject: semaphore. + Smalltalk unregisterExternalObject: readSemaphore. + Smalltalk unregisterExternalObject: writeSemaphore. + "Stash the semaphores and nil them before signaling to make sure + no caller gets a chance to wait on them again and block forever." + saveSemaphores := { + semaphore. + readSemaphore. + writeSemaphore }. + semaphore := readSemaphore := writeSemaphore := nil. + "A single #signal should be sufficient, as multiple processes trying to + read or write at once will result in undefined behavior anyway as their + data gets all mixed up together." + saveSemaphores do: [ :each | each signal ]. + self unregister ] +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> retryIfWaitingForConnection: aBlock [ + + ^ aBlock + on: ExpectedSocketFailure + do: [ :e | + self isWaitingForConnection + ifTrue: [ + self + waitForConnectionFor: Socket standardTimeout + ifClosed: nil + ifTimedOut: nil. + aBlock value ] + ifFalse: [ e pass ] ] +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> socketError [ + + ^ socketHandle ifNotNil: [ self primSocketError: socketHandle ] +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> socketErrorMessage [ + + ^ self socketError + ifNil: [ 'Socket destroyed, cannot retrieve error message' ] + ifNotNil: [ :err | + [ OSPlatform current getErrorMessage: err ] + on: Error + do: [ 'Error code: ' , err printString ] ] +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> waitForAcceptFor: timeout ifClosed: closedBlock ifTimedOut: timeoutBlock [ + "Wait and accept an incoming connection" + + self + waitForConnectionFor: timeout + ifClosed: [ ^ closedBlock value ] + ifTimedOut: [ ^ timeoutBlock value ]. + ^ self accept +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> waitForConnectionFor: timeout [ + "Wait up until the given deadline for a connection to be established. Return true if it is established by the deadline, false if not." + + ^ self + waitForConnectionFor: timeout + ifClosed: [ + ConnectionClosed signal: (socketHandle + ifNil: [ 'Socket destroyed while connecting' ] + ifNotNil: [ + 'Connection aborted or failed: ' , self socketErrorMessage ]) ] + ifTimedOut: [ + ConnectionTimedOut signal: + 'Failed to connect in ' , timeout asString , ' seconds' ] +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> waitForConnectionFor: timeout ifClosed: closedBlock ifTimedOut: timeoutBlock [ + "Wait up until the given deadline for a connection to be established. + Evaluate closedBlock if the connection is closed locally, + or timeoutBlock if the deadline expires. + + We should separately detect the case of a connection being refused here as well." + + | startTime msecsDelta msecsElapsed status | + startTime := Time millisecondClockValue. + msecsDelta := (timeout * 1000) truncated. + + [ + status := self primSocketConnectionStatus: socketHandle. + status == WaitingForConnection and: [ + (msecsElapsed := Time millisecondsSince: startTime) < msecsDelta ] ] + whileTrue: [ semaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ]. + + status == WaitingForConnection ifTrue: [ ^ timeoutBlock value ]. + status == Connected ifFalse: [ ^ closedBlock value ] +] + +{ #category : '*Ansible-Pharo-Pending-Patches' } +Socket >> waitForDataFor: timeout ifClosed: closedBlock ifTimedOut: timedOutBlock [ + "Wait for the given nr of seconds for data to arrive. + If it does not, execute . If the connection + is closed before any data arrives, execute ." + + | startTime msecsDelta msecsElapsed | + startTime := Time millisecondClockValue. + msecsDelta := (timeout * 1000) truncated. + [ self dataAvailable ] whileFalse: [ + self isConnected ifFalse: [ ^ closedBlock value ]. + (msecsElapsed := Time millisecondsSince: startTime) < msecsDelta + ifFalse: [ ^ timedOutBlock value ]. + readSemaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ] +] From e433939cb879ccd8f9bca7038b507f7246372030 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Fri, 30 Aug 2024 00:54:20 -0300 Subject: [PATCH 63/65] Add another expected error for compatibility with previous version of Pharo 12 --- source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st index 79c18d1b..c115d0a7 100644 --- a/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st +++ b/source/Ansible-RabbitMQ-Tests/RabbitMQClientTest.class.st @@ -339,10 +339,10 @@ RabbitMQClientTest >> testPublishingMessageWhenClientUnexpectedlyClosesConnectio publisher publishOnly: 'Test connection restored' onQueueNamed: self queueName ] assertingLogRecordsMatchRegexes: - { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to Connection close while waiting for data.' . + { '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to (Connection close while waiting for data.|primitive #primSocketSendDone\: in Socket failed|primitive #primSocketReceiveDataAvailable\: in Socket failed)' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' . - '\[ERROR\] AMQP Heartbeat failed unexpectedly \(connection closed while sending data\).' . - '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to connection closed while sending data' . + '\[ERROR\] AMQP Heartbeat failed unexpectedly \((connection closed while sending data|Socket destroyed, cannot retrieve error message|primitive #primSocketSendDone\: in Socket failed)\).' . + '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 closed due to (connection closed while sending data|Socket destroyed, cannot retrieve error message|primitive #primSocketSendDone\: in Socket failed)' . '\[INFO\] AMQP connection localhost\:(\d+)->localhost\:5672 established successfully' } ]. From aca79e7e229c03b7a33ffe573d1d2d0452e4a8a2 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Fri, 30 Aug 2024 01:41:59 -0300 Subject: [PATCH 64/65] Stop heartbeat after socket was closed --- source/Ansible-Protocol-Core/AmqpConnection.class.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index 22394a8a..b08b36b0 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -50,7 +50,6 @@ AmqpConnection >> becomeCloseAfter: aBlock [ LogRecord emitInfo: ( 'AMQP connection <1s> closed due to <2s>' expandMacrosWith: self connectionPairsDescription with: closingMethod replyText ). - heartbeatSender stop. [ socket close ] on: ExpectedSocketFailure do: [ :error | @@ -58,7 +57,8 @@ AmqpConnection >> becomeCloseAfter: aBlock [ will raise a SocketError that we want to silence " error return ]. isOpen := false. - socketConnectionStatus := ClosedSocketConnection dueTo: closingMethod replyText + socketConnectionStatus := ClosedSocketConnection dueTo: closingMethod replyText. + heartbeatSender stop. ] ] ] From f87db1e084435dd7fd0cfeffdd11635f51ed76d0 Mon Sep 17 00:00:00 2001 From: Juan Vanecek Date: Fri, 30 Aug 2024 02:56:14 -0300 Subject: [PATCH 65/65] Rename ensure to assert in messages that only checks something and raise error --- .../AmqpChannelHandler.class.st | 16 ++++---- .../AmqpConnection.class.st | 40 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st index 8e895066..3682c310 100644 --- a/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st +++ b/source/Ansible-Protocol-Core/AmqpChannelHandler.class.st @@ -16,6 +16,13 @@ Class { #package : 'Ansible-Protocol-Core' } +{ #category : 'handling' } +AmqpChannelHandler >> assertChannelOpen [ + + closeReason notNil + ifTrue: [ AmqpDisconnectedError signal: 'Channel closed' ] +] + { #category : 'accessing' } AmqpChannelHandler >> channelNumber [ @@ -67,13 +74,6 @@ AmqpChannelHandler >> dispatchCommand [ self resetState ] -{ #category : 'handling' } -AmqpChannelHandler >> ensureOpen [ - - closeReason notNil - ifTrue: [ AmqpDisconnectedError signal: 'Channel closed' ] -] - { #category : 'handling' } AmqpChannelHandler >> handleBodyFrame: frame [ @@ -167,7 +167,7 @@ AmqpChannelHandler >> waitForReply: acceptableReplies [ [ i > inbound size ] whileTrue: [ | currentConnection | - self ensureOpen. + self assertChannelOpen. currentConnection := connection mainLoopCycle. currentConnection whenConnected: [ ] diff --git a/source/Ansible-Protocol-Core/AmqpConnection.class.st b/source/Ansible-Protocol-Core/AmqpConnection.class.st index b08b36b0..2b04b3c3 100644 --- a/source/Ansible-Protocol-Core/AmqpConnection.class.st +++ b/source/Ansible-Protocol-Core/AmqpConnection.class.st @@ -38,6 +38,22 @@ AmqpConnection class >> to: aHostname extraProperties: aClientPropertyCollection ] +{ #category : 'connection-handling' } +AmqpConnection >> assertChannelOpen: channelNumber [ + + self assertSocketConnected. + ^ ( channels at: channelNumber ) assertChannelOpen +] + +{ #category : 'connection-handling' } +AmqpConnection >> assertSocketConnected [ + + ( socket isConnected and: [ socket isOtherEndClosed not ] ) ifFalse: [ + AmqpDisconnectedError signal: 'Connection closed' ]. + + ^ true +] + { #category : 'connection-handling' } AmqpConnection >> becomeCloseAfter: aBlock [ @@ -106,7 +122,7 @@ AmqpConnection >> createChannel [ | handler | - self ensureOpen. + self assertSocketConnected. handler := AmqpChannelHandler new connection: self. ^ AmqpChannel using: handler ] @@ -117,22 +133,6 @@ AmqpConnection >> credentials [ ^ credentials ] -{ #category : 'connection-handling' } -AmqpConnection >> ensureChannel: channelNumber [ - - self ensureOpen. - ^ ( channels at: channelNumber ) ensureOpen -] - -{ #category : 'connection-handling' } -AmqpConnection >> ensureOpen [ - - ( socket isConnected and: [ socket isOtherEndClosed not ] ) ifFalse: [ - AmqpDisconnectedError signal: 'Connection closed' ]. - - ^ true -] - { #category : 'connection-handling' } AmqpConnection >> handleConnectionClose: cmd [ @@ -347,7 +347,7 @@ AmqpConnection >> rpc: aRequestMethod onChannel: aChannelNumber ifConnectionClos ^ [ | ch | - ch := self ensureChannel: aChannelNumber. + ch := self assertChannelOpen: aChannelNumber. self sendMethod: aRequestMethod onChannel: aChannelNumber. ch waitForReply: aRequestMethod acceptableResponseClasses ] @@ -395,7 +395,7 @@ AmqpConnection >> sendMethod: aMethod properties: aProperties body: aByteArray o | pos | - self ensureChannel: channelNumber. + self assertChannelOpen: channelNumber. self sendFrame: ( AmqpMethodFrame new @@ -483,7 +483,7 @@ AmqpConnection >> virtualHost [ { #category : 'connection-handling' } AmqpConnection >> waitForEvent [ - [ self ensureOpen ] whileTrue: [ self mainLoopCycle ] + [ self assertSocketConnected ] whileTrue: [ self mainLoopCycle ] ] { #category : 'connection-handling' }