Compare commits
10 Commits
a0dbb5594d
...
171dca447c
| Author | SHA1 | Date |
|---|---|---|
|
|
171dca447c | |
|
|
ccbc8d1c16 | |
|
|
25962eccd1 | |
|
|
ebc9a3435c | |
|
|
ca3cc92d5c | |
|
|
f52c7983ce | |
|
|
d84c3d22de | |
|
|
6cd3f90025 | |
|
|
24649319d8 | |
|
|
8ffd1e6fc3 |
|
|
@ -1,21 +1,21 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
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
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,28 @@
|
||||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
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
|
eT5xfQPC5smWZlI0l0JK1bs=
|
||||||
orqH4SABIAbYTwNOb7k/wOc4EfucawBqMG4g+29qewD67+EXjB0GadqOXRoQyhRq
|
-----END PRIVATE KEY-----
|
||||||
hd74uUK5gzJOqStqiowQ0A==
|
|
||||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,22 +1,22 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIDvDCCAqSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH
|
MIIDvDCCAqSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBLMTowOAYDVQQDDDFUTFNH
|
||||||
ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDIzLTEwLTA4VDA4OjE2OjAzLjU5MDQ1NDEN
|
ZW5TZWxmU2lnbmVkUm9vdENBIDIwMjUtMDItMjdUMTU6NDQ6NTguODgwNTMwMQ0w
|
||||||
MAsGA1UEBwwEJCQkJDAeFw0yMzEwMDgxNTE2MDNaFw0zMzEwMDUxNTE2MDNaMCUx
|
CwYDVQQHDAQkJCQkMB4XDTI1MDIyNzE0NDQ1OVoXDTM1MDIyNTE0NDQ1OVowJTES
|
||||||
EjAQBgNVBAMMCWxvY2FsaG9zdDEPMA0GA1UECgwGY2xpZW50MIIBIjANBgkqhkiG
|
MBAGA1UEAwwJbG9jYWxob3N0MQ8wDQYDVQQKDAZjbGllbnQwggEiMA0GCSqGSIb3
|
||||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoOGcKsURRZG0D89J8rGcolZVqX56rDgA0Ma
|
DQEBAQUAA4IBDwAwggEKAoIBAQC7L/xjD4iHTCf2IfXd/fayxkX0+dI+Z2y+latM
|
||||||
cn4AosMQTZ86XAq+Ygn6QVcFV3NjuHxb29vsZfjSYbBpgQNLfpXN9EfeswVvaJND
|
UFvn4GpDIz0Acfqjp3/NhShbWoHqOhR/w5l20J9Ljt2RmecpybK717Flst8Q0g0C
|
||||||
wblKdRo10RTPslFewI4Aac88GXva+3DBMCwv3viI2S69apcuZgGw0+EKDh+JmbcM
|
xm3GaN7fVLAxoWAIbzU7cAZMv0SRuu2RIo2HTt5i2xBljA5Bf6wMZqMFxvnNWNGt
|
||||||
sdH81hZhYjmrS529qSOIji8vJYFTCQPMbGN17elnA7pZaHEmPKj5mzm0veSBvCwU
|
TIWVUzCjeqWqPUi84XdHu0GWyQ11rIjCnw5zY3D8EFc+HoTgI33y81EABps7ybmH
|
||||||
OZORr4eFE7Nct5RmhLm8DWT0EBRUWT8D6/b6+0ln32Yv30YNpKrua5wkn+kxsvKJ
|
BdUtMsAFEXgk3lJplaLeIvlM/HzBk+ffkqpcwC6kTnoR7Nww8a2aE6wHq91Hj+R7
|
||||||
tQRRKYRyfegSj6mo6L4za1ZvwV/JMN5mDLQUajvtOCsD4NpKcQIDAQABo4HPMIHM
|
mmAo8Hpx0grott/pmwWOd2Ld1w3gxC3I7D6yqjfT4Rjc6FyxAgMBAAGjgdAwgc0w
|
||||||
MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMCoG
|
CQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwKwYD
|
||||||
A1UdEQQjMCGCCWxvY2FsaG9zdIIJUFJPS09GSUVWgglsb2NhbGhvc3QwMQYDVR0f
|
VR0RBCQwIoIJbG9jYWxob3N0ggpGMjNOMDQ5MlhUgglsb2NhbGhvc3QwMQYDVR0f
|
||||||
BCowKDAmoCSgIoYgaHR0cDovL2NybC1zZXJ2ZXI6ODAwMC9iYXNpYy5jcmwwHQYD
|
BCowKDAmoCSgIoYgaHR0cDovL2NybC1zZXJ2ZXI6ODAwMC9iYXNpYy5jcmwwHQYD
|
||||||
VR0OBBYEFLPquWS+kT4+JE+cssrriRkL9UADMB8GA1UdIwQYMBaAFNSsn21DVr1X
|
VR0OBBYEFLmThoy0pKufr0QWZRwg1FJGdcFRMB8GA1UdIwQYMBaAFDJd0t924S/4
|
||||||
hhqmU+wMnLWFZc55MA0GCSqGSIb3DQEBCwUAA4IBAQC1Pz8SahCsQyiyuu6dz391
|
0cxm/LgBIUfoEhlaMA0GCSqGSIb3DQEBCwUAA4IBAQCk4Ytqqtymc8h0M2HiIyhK
|
||||||
KENabMpCwb/2wxljN5lfkOvvUrVmupld8/5nIdN2drL9jCrfbBz5ZRz+9Ryb8yrc
|
p2Dkf7GZRjBPvC6ULIxMEixslcDCkVTkLaYKRJL7xv37RNfc6kgi9K1IjPfDUtEm
|
||||||
sioH8Y9RNU5Gc3UJo7aAoMx4sIib6uJ+UO4fVlVvD4cN2h2sLHxtkI173Oo7lnMf
|
IDm56hRhIvLkH/BsUbhhJsZnYBN1GbqmFNtNP7Zj2Yt6uAwFkFB6gnK7RflSwVaG
|
||||||
4c+75iyZYdkEDXaOk+UbR8dncCj84y1Sbt0FYfCMT688O4HYkIGA3xGmqyX7PYV/
|
EYZhs8QEmZ1VhGymJorp5HGI6EcVkOhG3pScp5yaAqM2cKy7CLnZJfpCzQ12LZ7/
|
||||||
CP8CNKwJEuZpQRaGdClkmAmoEPyuFW9ec+A9gOrgCpuFJBI4MRcicC5Q+qmx+LTM
|
2UEKRtfILvN8kWaWOaGCM7t3Z2i6bfEh/1WZBmZnyK+zDBxv/YDp2iave/i7r/dY
|
||||||
pZ2louMnnlTRoj3tL4aDgfdwV0YGxyIjIzuYLy6QCF8MZ/TLwPK0C3oXXuYmCLBO
|
tOZA1KB2OMWZY4pHmiEior05yf0o7xNctPdwy3+IvRYAH6FJhMA29XoizPW8Cvtk
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,28 @@
|
||||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
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
|
ValHWT50FbbgAY337QwDjO8=
|
||||||
plfcGb027WE4DHqTzW73nbnK+NwkP3lLORbro7KWDFdNKP3v/Rx0Uq7CPGVN994G
|
-----END PRIVATE KEY-----
|
||||||
tH7wyGheZNTMtKDFGrAdOqse9/sKcTF3thaqYqXgDDZY
|
|
||||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,22 +1,22 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIDvDCCAqSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH
|
MIIDvDCCAqSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBLMTowOAYDVQQDDDFUTFNH
|
||||||
ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDIzLTEwLTA4VDA4OjE2OjAzLjU5MDQ1NDEN
|
ZW5TZWxmU2lnbmVkUm9vdENBIDIwMjUtMDItMjdUMTU6NDQ6NTguODgwNTMwMQ0w
|
||||||
MAsGA1UEBwwEJCQkJDAeFw0yMzEwMDgxNTE2MDNaFw0zMzEwMDUxNTE2MDNaMCUx
|
CwYDVQQHDAQkJCQkMB4XDTI1MDIyNzE0NDQ1OVoXDTM1MDIyNTE0NDQ1OVowJTES
|
||||||
EjAQBgNVBAMMCWxvY2FsaG9zdDEPMA0GA1UECgwGc2VydmVyMIIBIjANBgkqhkiG
|
MBAGA1UEAwwJbG9jYWxob3N0MQ8wDQYDVQQKDAZzZXJ2ZXIwggEiMA0GCSqGSIb3
|
||||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2dxp0wR++oE89W/mhEL7/XfJfo8iDbKKciUP
|
DQEBAQUAA4IBDwAwggEKAoIBAQCn1MRZTV3ATEvS8jFXhci/HGup4acSa1AduNak
|
||||||
PyIgBvggv625HifmEJG+epl77KinbCuZdc0DX/2FKH6HPM/tC6VcWB2cZRSHpBSM
|
8fpGHSFFmrywY6cl00rmPa95nfGloqbkRydqOwMn1Pv3XfHc3UeaiBgU+FNRj9u6
|
||||||
aieRV4yiaUFTqlOgQalJyRczRtv35QPdaIcDOX4lOw887sn6sJuZY5FtAyDr3opA
|
NOwJ0zR3QkqLxvQqbjrvxMN/IaZ2WL0Zem+j8YIY9yHytjkLEX2AH9AZLwHpdBLI
|
||||||
gZWLR+6fqi0YWqp5wqaz3hMzTGEEuu/ZKSqMWURRvp+Voz13auiShvhRb9hsdRp0
|
vSVeS3BNF/gKpXYExGNNfG47/Lo0fIgwboN069pHY/Ff80SAzUkzRcOxDplJoMWp
|
||||||
zf12Y9wGhWjOg7G6v1r/BP6/Nr1gWrgNUhuomSFC1FCRdCr1VrLpUfG3VNloVEOG
|
wym15ssmAnGzAzTrMhKIJ7rUyaE0ZNAIcid7KQ1VzB+yMpeYz5pdbx0G4U/DuVXf
|
||||||
mbWYfo+cDN6fV+PDlVB5UQp9YciFfpGXBzSXgNcsk8fEXpg8IQIDAQABo4HPMIHM
|
j8FnwlGwGAw05CckDjZcgrWNgLz1kqEcMV/UEFlbQuEzl5kTAgMBAAGjgdAwgc0w
|
||||||
MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMCoG
|
CQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwKwYD
|
||||||
A1UdEQQjMCGCCWxvY2FsaG9zdIIJUFJPS09GSUVWgglsb2NhbGhvc3QwHQYDVR0O
|
VR0RBCQwIoIJbG9jYWxob3N0ggpGMjNOMDQ5MlhUgglsb2NhbGhvc3QwHQYDVR0O
|
||||||
BBYEFPezEEGf7j3HedbaRCh4/FHT2VXrMB8GA1UdIwQYMBaAFNSsn21DVr1Xhhqm
|
BBYEFGv69aUODEtJA5QWU4KalMtGvuGYMB8GA1UdIwQYMBaAFDJd0t924S/40cxm
|
||||||
U+wMnLWFZc55MDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwtc2VydmVyOjgw
|
/LgBIUfoEhlaMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwtc2VydmVyOjgw
|
||||||
MDAvYmFzaWMuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQBLeagmroj4FFOXgUqDQo7i
|
MDAvYmFzaWMuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQBQxX+IwLmt9emhC/of3riN
|
||||||
kGCBZuCmn6GnCYdwEHtMoysGZ3vNFsB1BCug4fTuL7OU1l+Xw8iVnIvnGBpKypmt
|
wQaLXGYKKMHcsimGkBsQbitWlwWtBZwR2F9aOlvcOAlFbQ2Enldbdpkens1YwR4k
|
||||||
b7h9dN6urty0ewCS4WO8BTZUIdc1RJMo9N+nEMTja+5cqXHtO/VQnO2eqeALWJUU
|
Fsx2VdOnumSYbq6DKZg0mMrg3AqufYLBGVPSGNksQ6qERZVD5NGATLh0kA9R3q0h
|
||||||
IDPycb6HcTkHGFX0QDwxsPuMFL3p5HGr6U0llLF0J5FedxUA/YLLVCStofrWvBGT
|
eGKJbHyrdI6fkSELkmBGbuetjmGIfmYh+OjYZhqvU5mutjdOfY9k1t08eRvdNiIB
|
||||||
PKngh7S6ntaIUnTvwyzY2kPJ+byqRDNrL5jdavw1U8cGh1vi3k9mf1Uloi0mnAMT
|
4HxFVEk/S0opA98LkjY0wjPSAMZAWPNxHD5vHoaI6VwYnxLadD1NcasfEpae6uLW
|
||||||
kqOPzbQmHIQjxIOwqp2xkObXgqz1b0KNDfRDTwp90wzVxOCF5JJBCAIjPyLuncDv
|
t7CT+v6rtfBXvczfdd9rmhCmcHR5ckrL/wbpnvgkloQqxclw5IpDt/JkPyGghWx3
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,28 @@
|
||||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
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
|
rRdolD9MXCQidIrqwsPyHprv
|
||||||
TBUOtW3Y7AzyojbPiScvjmT+aoVwAZ3juHnUuxEuyUcI3WokkWPpllcaGd95sCUG
|
-----END PRIVATE KEY-----
|
||||||
7iR90VPBJ/meYyQMYY1BGq4ngi5DvLGy6K/pS5CHPi0U
|
|
||||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
[
|
||||||
|
{rabbitmq_auth_backend_oauth2, [{key_config,
|
||||||
|
[{signing_keys,
|
||||||
|
#{<<"token-key">> =>
|
||||||
|
{map,
|
||||||
|
#{<<"alg">> => <<"HS256">>,
|
||||||
|
<<"k">> => <<"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH">>,
|
||||||
|
<<"kid">> => <<"token-key">>,
|
||||||
|
<<"kty">> => <<"oct">>,
|
||||||
|
<<"use">> => <<"sig">>,
|
||||||
|
<<"value">> => <<"token-key">>}}}}]},
|
||||||
|
{resource_server_id,<<"rabbitmq">>}]}
|
||||||
|
].
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"rabbit_version":"4.1.0-beta.4","rabbitmq_version":"4.1.0-beta.4","product_name":"RabbitMQ","product_version":"4.1.0-beta.4","rabbitmq_definition_format":"cluster","original_cluster_name":"rabbit@rabbitmq-amqp-go-client-rabbitmq","explanation":"Definitions of cluster 'rabbit@rabbitmq-amqp-go-client-rabbitmq'","users":[{"name":"guest","password_hash":"5AXVjnnJAKWzGy8L/t9vhOi5iZ4j2wwUA9aI0QoOgBYPXmGS","hashing_algorithm":"rabbit_password_hashing_sha256","tags":["administrator"],"limits":{}},{"name":"user_1","password_hash":"k91LVmfv+JsXCihK+BiwURDo2otPX4wRtX4vErArkhRq/kkJ","hashing_algorithm":"rabbit_password_hashing_sha256","tags":["administrator"],"limits":{}},{"name":"O=client,CN=localhost","password_hash":"n3z/QaCVGTgelie+hmxw7//jYQmtERIVOQj+tw47AoPVAsCh","hashing_algorithm":"rabbit_password_hashing_sha256","tags":["administrator"],"limits":{}}],"vhosts":[{"name":"vhost_user_1","description":"","metadata":{"description":"","tags":[],"default_queue_type":"classic"},"tags":[],"default_queue_type":"classic"},{"name":"/","description":"Default virtual host","metadata":{"description":"Default virtual host","tags":[],"default_queue_type":"classic"},"tags":[],"default_queue_type":"classic"},{"name":"tls","description":"","metadata":{"description":"","tags":[],"default_queue_type":"classic"},"tags":[],"default_queue_type":"classic"}],"permissions":[{"user":"O=client,CN=localhost","vhost":"/","configure":".*","write":".*","read":".*"},{"user":"guest","vhost":"/","configure":".*","write":".*","read":".*"},{"user":"O=client,CN=localhost","vhost":"tls","configure":".*","write":".*","read":".*"},{"user":"guest","vhost":"vhost_user_1","configure":".*","write":".*","read":".*"},{"user":"guest","vhost":"tls","configure":".*","write":".*","read":".*"},{"user":"user_1","vhost":"vhost_user_1","configure":".*","write":".*","read":".*"}],"topic_permissions":[],"parameters":[],"global_parameters":[{"name":"cluster_tags","value":[]},{"name":"internal_cluster_id","value":"rabbitmq-cluster-id-A5bx3jtkxi8ukG64KRkw8g"}],"policies":[],"queues":[],"exchanges":[],"bindings":[]}
|
||||||
|
|
@ -1 +1 @@
|
||||||
[rabbitmq_auth_mechanism_ssl,rabbitmq_management,rabbitmq_stream,rabbitmq_stream_management,rabbitmq_top].
|
[rabbitmq_auth_mechanism_ssl,rabbitmq_management,rabbitmq_stream,rabbitmq_stream_management,rabbitmq_top,rabbitmq_auth_backend_oauth2].
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,7 @@ set -o xtrace
|
||||||
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
readonly script_dir
|
readonly script_dir
|
||||||
echo "[INFO] script_dir: '$script_dir'"
|
echo "[INFO] script_dir: '$script_dir'"
|
||||||
|
readonly rabbitmq_image=rabbitmq:4.1.0-beta.4-management-alpine
|
||||||
if [[ $3 == 'arm' ]]
|
|
||||||
then
|
|
||||||
readonly rabbitmq_image="${RABBITMQ_IMAGE:-pivotalrabbitmq/rabbitmq-arm64:main}"
|
|
||||||
else
|
|
||||||
readonly rabbitmq_image="${RABBITMQ_IMAGE:-pivotalrabbitmq/rabbitmq:main}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
readonly docker_name_prefix='rabbitmq-amqp-go-client'
|
readonly docker_name_prefix='rabbitmq-amqp-go-client'
|
||||||
|
|
@ -91,6 +85,8 @@ function start_rabbitmq
|
||||||
--network "$docker_network_name" \
|
--network "$docker_network_name" \
|
||||||
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/enabled_plugins:/etc/rabbitmq/enabled_plugins" \
|
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/enabled_plugins:/etc/rabbitmq/enabled_plugins" \
|
||||||
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro" \
|
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro" \
|
||||||
|
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/definitions.json:/etc/rabbitmq/definitions.json:ro" \
|
||||||
|
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/advanced.config:/etc/rabbitmq/advanced.config:ro" \
|
||||||
--volume "$GITHUB_WORKSPACE/.ci/certs:/etc/rabbitmq/certs:ro" \
|
--volume "$GITHUB_WORKSPACE/.ci/certs:/etc/rabbitmq/certs:ro" \
|
||||||
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/log:/var/log/rabbitmq" \
|
--volume "$GITHUB_WORKSPACE/.ci/ubuntu/log:/var/log/rabbitmq" \
|
||||||
"$rabbitmq_image"
|
"$rabbitmq_image"
|
||||||
|
|
@ -163,8 +159,7 @@ function install_ca_certificate
|
||||||
openssl s_client -connect localhost:5671 \
|
openssl s_client -connect localhost:5671 \
|
||||||
-CAfile "$GITHUB_WORKSPACE/.ci/certs/ca_certificate.pem" \
|
-CAfile "$GITHUB_WORKSPACE/.ci/certs/ca_certificate.pem" \
|
||||||
-cert "$GITHUB_WORKSPACE/.ci/certs/client_localhost_certificate.pem" \
|
-cert "$GITHUB_WORKSPACE/.ci/certs/client_localhost_certificate.pem" \
|
||||||
-key "$GITHUB_WORKSPACE/.ci/certs/client_localhost_key.pem" \
|
-key "$GITHUB_WORKSPACE/.ci/certs/client_localhost_key.pem"
|
||||||
-pass pass:grapefruit < /dev/null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
docker network create "$docker_network_name" || echo "[INFO] network '$docker_network_name' is already created"
|
docker network create "$docker_network_name" || echo "[INFO] network '$docker_network_name' is already created"
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,14 @@ ssl_options.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem
|
||||||
ssl_options.certfile = /etc/rabbitmq/certs/server_localhost_certificate.pem
|
ssl_options.certfile = /etc/rabbitmq/certs/server_localhost_certificate.pem
|
||||||
ssl_options.keyfile = /etc/rabbitmq/certs/server_localhost_key.pem
|
ssl_options.keyfile = /etc/rabbitmq/certs/server_localhost_key.pem
|
||||||
ssl_options.verify = verify_peer
|
ssl_options.verify = verify_peer
|
||||||
ssl_options.password = grapefruit
|
|
||||||
ssl_options.depth = 1
|
ssl_options.depth = 1
|
||||||
ssl_options.fail_if_no_peer_cert = false
|
ssl_options.fail_if_no_peer_cert = false
|
||||||
|
|
||||||
auth_mechanisms.1 = PLAIN
|
auth_mechanisms.1 = PLAIN
|
||||||
auth_mechanisms.2 = ANONYMOUS
|
auth_mechanisms.2 = ANONYMOUS
|
||||||
auth_mechanisms.3 = EXTERNAL
|
auth_mechanisms.3 = EXTERNAL
|
||||||
|
|
||||||
|
auth_backends.1 = internal
|
||||||
|
auth_backends.2 = rabbit_auth_backend_oauth2
|
||||||
|
|
||||||
|
load_definitions = /etc/rabbitmq/definitions.json
|
||||||
|
|
|
||||||
26
Makefile
26
Makefile
|
|
@ -1,3 +1,10 @@
|
||||||
|
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||||
|
ifeq (,$(shell go env GOBIN))
|
||||||
|
GOBIN = $(shell go env GOPATH)/bin
|
||||||
|
else
|
||||||
|
GOBIN = $(shell go env GOBIN)
|
||||||
|
endif
|
||||||
|
|
||||||
all: test
|
all: test
|
||||||
|
|
||||||
format:
|
format:
|
||||||
|
|
@ -5,8 +12,21 @@ format:
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./pkg/rabbitmqamqp
|
go vet ./pkg/rabbitmqamqp
|
||||||
|
go vet ./docs/examples/...
|
||||||
|
|
||||||
test: format vet
|
STATICCHECK ?= $(GOBIN)/staticcheck
|
||||||
|
STATICCHECK_VERSION ?= latest
|
||||||
|
$(STATICCHECK):
|
||||||
|
go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION)
|
||||||
|
check: $(STATICCHECK)
|
||||||
|
$(STATICCHECK) ./pkg/rabbitmqamqp
|
||||||
|
$(STATICCHECK) ./docs/examples/...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test: format vet check
|
||||||
cd ./pkg/rabbitmqamqp && go run -mod=mod github.com/onsi/ginkgo/v2/ginkgo \
|
cd ./pkg/rabbitmqamqp && go run -mod=mod github.com/onsi/ginkgo/v2/ginkgo \
|
||||||
--randomize-all --randomize-suites \
|
--randomize-all --randomize-suites \
|
||||||
--cover --coverprofile=coverage.txt --covermode=atomic \
|
--cover --coverprofile=coverage.txt --covermode=atomic \
|
||||||
|
|
@ -14,8 +34,8 @@ test: format vet
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rabbitmq-server-start-arm:
|
rabbitmq-server-start:
|
||||||
./.ci/ubuntu/gha-setup.sh start pull arm
|
./.ci/ubuntu/gha-setup.sh start pull
|
||||||
|
|
||||||
rabbitmq-server-stop:
|
rabbitmq-server-stop:
|
||||||
./.ci/ubuntu/gha-setup.sh stop
|
./.ci/ubuntu/gha-setup.sh stop
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
# RabbitMQ AMQP 1.0 Golang Client
|
# RabbitMQ AMQP 1.0 Golang Client
|
||||||
|
|
||||||
This library is meant to be used with RabbitMQ 4.0.
|
This library is meant to be used with RabbitMQ 4.0. (2025.06.26)
|
||||||
Suitable for testing in pre-production environments.
|
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
- [Getting Started](docs/examples/getting_started)
|
- [Getting Started](docs/examples/getting_started)
|
||||||
- [Examples](docs/examples)
|
- [Examples](docs/examples)
|
||||||
|
Inside the `docs/examples` directory you will find several examples to get you started.</br>
|
||||||
|
Also advanced examples like how to use streams, how to handle reconnections, and how to use TLS.
|
||||||
- Getting started Video tutorial: </br>
|
- Getting started Video tutorial: </br>
|
||||||
[](https://youtu.be/iR1JUFh3udI)
|
[](https://youtu.be/iR1JUFh3udI)
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ Suitable for testing in pre-production environments.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Client Guide](https://www.rabbitmq.com/client-libraries/amqp-client-libraries) (work in progress for this client)
|
- [Client Guide](https://www.rabbitmq.com/client-libraries/amqp-client-libraries)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,7 @@
|
||||||
- [Streams](streams) - An example of how to use [RabbitMQ Streams](https://www.rabbitmq.com/docs/streams) with AMQP 1.0
|
- [Streams](streams) - An example of how to use [RabbitMQ Streams](https://www.rabbitmq.com/docs/streams) with AMQP 1.0
|
||||||
- [Stream Filtering](streams_filtering) - An example of how to use streams [Filter Expressions](https://www.rabbitmq.com/blog/2024/12/13/amqp-filter-expressions)
|
- [Stream Filtering](streams_filtering) - An example of how to use streams [Filter Expressions](https://www.rabbitmq.com/blog/2024/12/13/amqp-filter-expressions)
|
||||||
- [Publisher per message target](publisher_msg_targets) - An example of how to use a single publisher to send messages in different queues with the address to the message target in the message properties.
|
- [Publisher per message target](publisher_msg_targets) - An example of how to use a single publisher to send messages in different queues with the address to the message target in the message properties.
|
||||||
- [Video](video) - From the YouTube tutorial [AMQP 1.0 with Golang](https://youtu.be/iR1JUFh3udI)
|
- [Video](video) - From the YouTube tutorial [AMQP 1.0 with Golang](https://youtu.be/iR1JUFh3udI)
|
||||||
|
- [TLS](tls) - An example of how to use TLS with the AMQP 1.0 client.
|
||||||
|
- [Advanced Settings](advanced_settings) - An example of how to use the advanced connection settings of the AMQP 1.0 client.
|
||||||
|
- [Broadcast](broadcast) - An example of how to use fanout to broadcast messages to multiple auto-deleted queues.
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
|
rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
rmq.Info("Golang AMQP 1.0 Advanced connection settings")
|
||||||
|
|
||||||
|
// rmq.NewClusterEnvironment setups the environment.
|
||||||
|
// define multiple endpoints with different connection settings
|
||||||
|
// the connection will be created based on the strategy Sequential
|
||||||
|
env := rmq.NewClusterEnvironmentWithStrategy([]rmq.Endpoint{
|
||||||
|
|
||||||
|
//this is correct
|
||||||
|
{Address: "amqp://localhost:5672", Options: &rmq.AmqpConnOptions{
|
||||||
|
ContainerID: "My connection one ",
|
||||||
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
|
RecoveryConfiguration: &rmq.RecoveryConfiguration{
|
||||||
|
ActiveRecovery: false,
|
||||||
|
},
|
||||||
|
OAuth2Options: nil,
|
||||||
|
Id: "my first id",
|
||||||
|
}},
|
||||||
|
// this is correct
|
||||||
|
{Address: "amqp://localhost:5672", Options: &rmq.AmqpConnOptions{
|
||||||
|
ContainerID: "My connection two",
|
||||||
|
SASLType: amqp.SASLTypePlain("guest", "guest"),
|
||||||
|
RecoveryConfiguration: &rmq.RecoveryConfiguration{
|
||||||
|
ActiveRecovery: true,
|
||||||
|
BackOffReconnectInterval: 2 * time.Second,
|
||||||
|
MaxReconnectAttempts: 5,
|
||||||
|
},
|
||||||
|
OAuth2Options: nil,
|
||||||
|
Id: "my second id",
|
||||||
|
}},
|
||||||
|
|
||||||
|
//this end point is incorrect, so won't be used
|
||||||
|
//so another endpoint will be used
|
||||||
|
{Address: "amqp://wrong:5672", Options: &rmq.AmqpConnOptions{
|
||||||
|
ContainerID: "My connection wrong",
|
||||||
|
SASLType: amqp.SASLTypePlain("guest", "guest"),
|
||||||
|
RecoveryConfiguration: &rmq.RecoveryConfiguration{
|
||||||
|
ActiveRecovery: true,
|
||||||
|
BackOffReconnectInterval: 2 * time.Second,
|
||||||
|
MaxReconnectAttempts: 5,
|
||||||
|
},
|
||||||
|
OAuth2Options: nil,
|
||||||
|
Id: "my wrong id",
|
||||||
|
}},
|
||||||
|
}, rmq.StrategyRandom)
|
||||||
|
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
connection, err := env.NewConnection(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error opening connection", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rmq.Info("Connection opened", "Container ID", connection.Id())
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
}
|
||||||
|
// Here you should see the connection opened for the first two endpoints
|
||||||
|
// with the containers ID "My connection one" and with the containers ID "My connection two"
|
||||||
|
// press any key to exit
|
||||||
|
fmt.Println("Press any key to exit")
|
||||||
|
var input string
|
||||||
|
_, _ = fmt.Scanln(&input)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
|
||||||
|
|
||||||
|
// Open a connection to the AMQP 1.0 server ( RabbitMQ >= 4.0)
|
||||||
|
amqpConnection, err := env.NewConnection(context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error opening connection", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const broadcastExchange = "broadcast"
|
||||||
|
// Create the management interface for the connection
|
||||||
|
// so we can declare exchanges, queues, and bindings
|
||||||
|
management := amqpConnection.Management()
|
||||||
|
_, err = management.DeclareExchange(context.Background(), &rmq.FanOutExchangeSpecification{
|
||||||
|
Name: broadcastExchange,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error declaring exchange", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
// create temp queues
|
||||||
|
q, err := management.DeclareQueue(context.Background(), &rmq.AutoGeneratedQueueSpecification{
|
||||||
|
IsAutoDelete: true,
|
||||||
|
IsExclusive: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error DeclareQueue", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = management.Bind(context.TODO(), &rmq.ExchangeToQueueBindingSpecification{
|
||||||
|
SourceExchange: broadcastExchange,
|
||||||
|
DestinationQueue: q.Name(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error binding", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(idx int) {
|
||||||
|
consumer, err := amqpConnection.NewConsumer(context.Background(), q.Name(), nil)
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error creating consumer", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
dcx, err1 := consumer.Receive(context.Background())
|
||||||
|
if err1 != nil {
|
||||||
|
rmq.Error("Error receiving message", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rmq.Info("[Consumer]", "index", idx, "msg", fmt.Sprintf("%s", dcx.Message().Data), "[queue]", q.Name())
|
||||||
|
err1 = dcx.Accept(context.Background())
|
||||||
|
if err1 != nil {
|
||||||
|
rmq.Error("Error accepting message", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
publisher, err := amqpConnection.NewPublisher(context.Background(), &rmq.ExchangeAddress{
|
||||||
|
Exchange: broadcastExchange,
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error creating publisher", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10_000; i++ {
|
||||||
|
publishResult, err := publisher.Publish(context.Background(),
|
||||||
|
rmq.NewMessage([]byte("Hello AMQP 1.0 - id:"+fmt.Sprintf("%d", i))))
|
||||||
|
if err != nil {
|
||||||
|
rmq.Error("Error publishing message", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch publishResult.Outcome.(type) {
|
||||||
|
// publish result
|
||||||
|
case *rmq.StateAccepted:
|
||||||
|
rmq.Info("[Publisher] Message accepted", "message", publishResult.Message.GetData())
|
||||||
|
default:
|
||||||
|
rmq.Error("[Publisher] Message not accepted", "outcome", publishResult.Outcome)
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// press any key to close the connection
|
||||||
|
|
||||||
|
var input string
|
||||||
|
_, _ = fmt.Scanln(&input)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ func main() {
|
||||||
|
|
||||||
rmq.Info("Getting started with AMQP Go AMQP 1.0 Client")
|
rmq.Info("Getting started with AMQP Go AMQP 1.0 Client")
|
||||||
|
|
||||||
/// Create a channel to receive state change notifications
|
/// Create a channel to receive connection state change notifications
|
||||||
stateChanged := make(chan *rmq.StateChanged, 1)
|
stateChanged := make(chan *rmq.StateChanged, 1)
|
||||||
go func(ch chan *rmq.StateChanged) {
|
go func(ch chan *rmq.StateChanged) {
|
||||||
for statusChanged := range ch {
|
for statusChanged := range ch {
|
||||||
|
|
@ -23,10 +23,16 @@ func main() {
|
||||||
}
|
}
|
||||||
}(stateChanged)
|
}(stateChanged)
|
||||||
|
|
||||||
// rmq.NewEnvironment setups the environment.
|
// rmq.NewClusterEnvironment setups the environment.
|
||||||
// The environment is used to create connections
|
// The environment is used to create connections
|
||||||
// given the same parameters
|
// given the same parameters
|
||||||
env := rmq.NewEnvironment([]string{"amqp://"}, nil)
|
env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
|
||||||
|
|
||||||
|
// in case you have multiple endpoints you can use the following:
|
||||||
|
//env := rmq.NewClusterEnvironment([]rmq.Endpoint{
|
||||||
|
// {Address: "amqp://server1", Options: &rmq.AmqpConnOptions{}},
|
||||||
|
// {Address: "amqp://server2", Options: &rmq.AmqpConnOptions{}},
|
||||||
|
//})
|
||||||
|
|
||||||
// Open a connection to the AMQP 1.0 server ( RabbitMQ >= 4.0)
|
// Open a connection to the AMQP 1.0 server ( RabbitMQ >= 4.0)
|
||||||
amqpConnection, err := env.NewConnection(context.Background())
|
amqpConnection, err := env.NewConnection(context.Background())
|
||||||
|
|
@ -74,7 +80,6 @@ func main() {
|
||||||
|
|
||||||
// Create a consumer to receive messages from the queue
|
// Create a consumer to receive messages from the queue
|
||||||
// you need to build the address of the queue, but you can use the helper function
|
// you need to build the address of the queue, but you can use the helper function
|
||||||
|
|
||||||
consumer, err := amqpConnection.NewConsumer(context.Background(), queueName, nil)
|
consumer, err := amqpConnection.NewConsumer(context.Background(), queueName, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rmq.Error("Error creating consumer", err)
|
rmq.Error("Error creating consumer", err)
|
||||||
|
|
@ -89,16 +94,16 @@ func main() {
|
||||||
deliveryContext, err := consumer.Receive(ctx)
|
deliveryContext, err := consumer.Receive(ctx)
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
// The consumer was closed correctly
|
// The consumer was closed correctly
|
||||||
rmq.Info("[NewConsumer]", "consumer closed. Context", err)
|
rmq.Info("[Consumer]", "consumer closed. Context", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// An error occurred receiving the message
|
// An error occurred receiving the message
|
||||||
rmq.Error("[NewConsumer]", "Error receiving message", err)
|
rmq.Error("[Consumer]", "Error receiving message", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rmq.Info("[NewConsumer]", "Received message",
|
rmq.Info("[Consumer]", "Received message",
|
||||||
fmt.Sprintf("%s", deliveryContext.Message().Data))
|
fmt.Sprintf("%s", deliveryContext.Message().Data))
|
||||||
|
|
||||||
err = deliveryContext.Accept(context.Background())
|
err = deliveryContext.Accept(context.Background())
|
||||||
|
|
@ -112,7 +117,7 @@ func main() {
|
||||||
publisher, err := amqpConnection.NewPublisher(context.Background(), &rmq.ExchangeAddress{
|
publisher, err := amqpConnection.NewPublisher(context.Background(), &rmq.ExchangeAddress{
|
||||||
Exchange: exchangeName,
|
Exchange: exchangeName,
|
||||||
Key: routingKey,
|
Key: routingKey,
|
||||||
}, "getting-started-publisher")
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rmq.Error("Error creating publisher", err)
|
rmq.Error("Error creating publisher", err)
|
||||||
return
|
return
|
||||||
|
|
@ -128,18 +133,15 @@ func main() {
|
||||||
}
|
}
|
||||||
switch publishResult.Outcome.(type) {
|
switch publishResult.Outcome.(type) {
|
||||||
case *rmq.StateAccepted:
|
case *rmq.StateAccepted:
|
||||||
rmq.Info("[NewPublisher]", "Message accepted", publishResult.Message.Data[0])
|
rmq.Info("[Publisher]", "Message accepted", publishResult.Message.Data[0])
|
||||||
break
|
|
||||||
case *rmq.StateReleased:
|
case *rmq.StateReleased:
|
||||||
rmq.Warn("[NewPublisher]", "Message was not routed", publishResult.Message.Data[0])
|
rmq.Warn("[Publisher]", "Message was not routed", publishResult.Message.Data[0])
|
||||||
break
|
|
||||||
case *rmq.StateRejected:
|
case *rmq.StateRejected:
|
||||||
rmq.Warn("[NewPublisher]", "Message rejected", publishResult.Message.Data[0])
|
rmq.Warn("[Publisher]", "Message rejected", publishResult.Message.Data[0])
|
||||||
stateType := publishResult.Outcome.(*rmq.StateRejected)
|
stateType := publishResult.Outcome.(*rmq.StateRejected)
|
||||||
if stateType.Error != nil {
|
if stateType.Error != nil {
|
||||||
rmq.Warn("[NewPublisher]", "Message rejected with error: %v", stateType.Error)
|
rmq.Warn("[Publisher]", "Message rejected with error: %v", stateType.Error)
|
||||||
}
|
}
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
||||||
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
||||||
|
|
@ -156,13 +158,13 @@ func main() {
|
||||||
//Close the consumer
|
//Close the consumer
|
||||||
err = consumer.Close(context.Background())
|
err = consumer.Close(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rmq.Error("[NewConsumer]", err)
|
rmq.Error("[Consumer]", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Close the publisher
|
// Close the publisher
|
||||||
err = publisher.Close(context.Background())
|
err = publisher.Close(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rmq.Error("[NewPublisher]", err)
|
rmq.Error("[Publisher]", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@ func checkError(err error) {
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
rmq.Info("Define the publisher message targets")
|
rmq.Info("Define the publisher message targets")
|
||||||
|
env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
|
||||||
env := rmq.NewEnvironment([]string{"amqp://"}, nil)
|
|
||||||
amqpConnection, err := env.NewConnection(context.Background())
|
amqpConnection, err := env.NewConnection(context.Background())
|
||||||
checkError(err)
|
checkError(err)
|
||||||
queues := []string{"queue1", "queue2", "queue3"}
|
queues := []string{"queue1", "queue2", "queue3"}
|
||||||
|
|
@ -31,7 +30,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a publisher without a target
|
// create a publisher without a target
|
||||||
publisher, err := amqpConnection.NewPublisher(context.TODO(), nil, "stream-publisher")
|
publisher, err := amqpConnection.NewPublisher(context.TODO(), nil, nil)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
// publish messages to the stream
|
// publish messages to the stream
|
||||||
|
|
@ -55,7 +54,6 @@ func main() {
|
||||||
switch publishResult.Outcome.(type) {
|
switch publishResult.Outcome.(type) {
|
||||||
case *amqp.StateAccepted:
|
case *amqp.StateAccepted:
|
||||||
rmq.Info("[Publisher]", "Message accepted", publishResult.Message.Data[0])
|
rmq.Info("[Publisher]", "Message accepted", publishResult.Message.Data[0])
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
rmq.Warn("[Publisher]", "Message not accepted", publishResult.Message.Data[0])
|
rmq.Warn("[Publisher]", "Message not accepted", publishResult.Message.Data[0])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,19 @@ func main() {
|
||||||
var stateAccepted int32
|
var stateAccepted int32
|
||||||
var stateReleased int32
|
var stateReleased int32
|
||||||
var stateRejected int32
|
var stateRejected int32
|
||||||
|
var isRunning bool
|
||||||
|
|
||||||
var received int32
|
var received int32
|
||||||
var failed int32
|
var failed int32
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
isRunning = true
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for isRunning {
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
total := stateAccepted + stateReleased + stateRejected
|
total := stateAccepted + stateReleased + stateRejected
|
||||||
messagesPerSecond := float64(total) / time.Since(startTime).Seconds()
|
messagesPerSecond := float64(total) / time.Since(startTime).Seconds()
|
||||||
rmq.Info("[Stats]", "sent", total, "received", received, "failed", failed, "messagesPerSecond", messagesPerSecond)
|
rmq.Info("[Stats]", "sent", total, "received", received, "failed", failed, "messagesPerSecond", messagesPerSecond)
|
||||||
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -41,12 +42,22 @@ func main() {
|
||||||
switch statusChanged.To.(type) {
|
switch statusChanged.To.(type) {
|
||||||
case *rmq.StateOpen:
|
case *rmq.StateOpen:
|
||||||
signalBlock.Broadcast()
|
signalBlock.Broadcast()
|
||||||
|
case *rmq.StateReconnecting:
|
||||||
|
rmq.Info("[connection]", "Reconnecting to the AMQP 1.0 server")
|
||||||
|
case *rmq.StateClosed:
|
||||||
|
StateClosed := statusChanged.To.(*rmq.StateClosed)
|
||||||
|
if errors.Is(StateClosed.GetError(), rmq.ErrMaxReconnectAttemptsReached) {
|
||||||
|
rmq.Error("[connection]", "Max reconnect attempts reached. Closing connection", StateClosed.GetError())
|
||||||
|
signalBlock.Broadcast()
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(stateChanged)
|
}(stateChanged)
|
||||||
|
|
||||||
// Open a connection to the AMQP 1.0 server
|
// Open a connection to the AMQP 1.0 server
|
||||||
amqpConnection, err := rmq.Dial(context.Background(), []string{"amqp://"}, &rmq.AmqpConnOptions{
|
amqpConnection, err := rmq.Dial(context.Background(), "amqp://", &rmq.AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
ContainerID: "reliable-amqp10-go",
|
ContainerID: "reliable-amqp10-go",
|
||||||
RecoveryConfiguration: &rmq.RecoveryConfiguration{
|
RecoveryConfiguration: &rmq.RecoveryConfiguration{
|
||||||
|
|
@ -87,13 +98,13 @@ func main() {
|
||||||
|
|
||||||
// Consume messages from the queue
|
// Consume messages from the queue
|
||||||
go func(ctx context.Context) {
|
go func(ctx context.Context) {
|
||||||
for {
|
for isRunning {
|
||||||
deliveryContext, err := consumer.Receive(ctx)
|
deliveryContext, err := consumer.Receive(ctx)
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
// The consumer was closed correctly
|
// The consumer was closed correctly
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil && isRunning {
|
||||||
// An error occurred receiving the message
|
// An error occurred receiving the message
|
||||||
// here the consumer could be disconnected from the server due to a network error
|
// here the consumer could be disconnected from the server due to a network error
|
||||||
signalBlock.L.Lock()
|
signalBlock.L.Lock()
|
||||||
|
|
@ -107,7 +118,7 @@ func main() {
|
||||||
|
|
||||||
atomic.AddInt32(&received, 1)
|
atomic.AddInt32(&received, 1)
|
||||||
err = deliveryContext.Accept(context.Background())
|
err = deliveryContext.Accept(context.Background())
|
||||||
if err != nil {
|
if err != nil && isRunning {
|
||||||
// same here the delivery could not be accepted due to a network error
|
// same here the delivery could not be accepted due to a network error
|
||||||
// we wait for 2_500 ms and try again
|
// we wait for 2_500 ms and try again
|
||||||
time.Sleep(2500 * time.Millisecond)
|
time.Sleep(2500 * time.Millisecond)
|
||||||
|
|
@ -118,18 +129,19 @@ func main() {
|
||||||
|
|
||||||
publisher, err := amqpConnection.NewPublisher(context.Background(), &rmq.QueueAddress{
|
publisher, err := amqpConnection.NewPublisher(context.Background(), &rmq.QueueAddress{
|
||||||
Queue: queueName,
|
Queue: queueName,
|
||||||
}, "reliable-publisher")
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rmq.Error("Error creating publisher", err)
|
rmq.Error("Error creating publisher", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
for i := 0; i < 1; i++ {
|
for i := 0; i < 1; i++ {
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
|
||||||
for i := 0; i < 500_000; i++ {
|
for i := 0; i < 500_000; i++ {
|
||||||
|
if !isRunning {
|
||||||
|
rmq.Info("[Publisher]", "Publisher is stopped simulation not running, queue", queueName)
|
||||||
|
return
|
||||||
|
}
|
||||||
publishResult, err := publisher.Publish(context.Background(), rmq.NewMessage([]byte("Hello, World!"+fmt.Sprintf("%d", i))))
|
publishResult, err := publisher.Publish(context.Background(), rmq.NewMessage([]byte("Hello, World!"+fmt.Sprintf("%d", i))))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// here you need to deal with the error. You can store the message in a local in memory/persistent storage
|
// here you need to deal with the error. You can store the message in a local in memory/persistent storage
|
||||||
|
|
@ -147,13 +159,10 @@ func main() {
|
||||||
switch publishResult.Outcome.(type) {
|
switch publishResult.Outcome.(type) {
|
||||||
case *rmq.StateAccepted:
|
case *rmq.StateAccepted:
|
||||||
atomic.AddInt32(&stateAccepted, 1)
|
atomic.AddInt32(&stateAccepted, 1)
|
||||||
break
|
|
||||||
case *rmq.StateReleased:
|
case *rmq.StateReleased:
|
||||||
atomic.AddInt32(&stateReleased, 1)
|
atomic.AddInt32(&stateReleased, 1)
|
||||||
break
|
|
||||||
case *rmq.StateRejected:
|
case *rmq.StateRejected:
|
||||||
atomic.AddInt32(&stateRejected, 1)
|
atomic.AddInt32(&stateRejected, 1)
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
||||||
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
||||||
|
|
@ -163,7 +172,6 @@ func main() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
println("press any key to close the connection")
|
println("press any key to close the connection")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ func main() {
|
||||||
|
|
||||||
rmq.Info("Golang AMQP 1.0 Streams example")
|
rmq.Info("Golang AMQP 1.0 Streams example")
|
||||||
queueStream := "stream-go-queue-" + time.Now().String()
|
queueStream := "stream-go-queue-" + time.Now().String()
|
||||||
env := rmq.NewEnvironment([]string{"amqp://"}, nil)
|
env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
|
||||||
|
|
||||||
amqpConnection, err := env.NewConnection(context.Background())
|
amqpConnection, err := env.NewConnection(context.Background())
|
||||||
checkError(err)
|
checkError(err)
|
||||||
management := amqpConnection.Management()
|
management := amqpConnection.Management()
|
||||||
|
|
@ -35,7 +36,7 @@ func main() {
|
||||||
|
|
||||||
// create a stream publisher. In this case we use the QueueAddress to make the example
|
// create a stream publisher. In this case we use the QueueAddress to make the example
|
||||||
// simple. So we use the default exchange here.
|
// simple. So we use the default exchange here.
|
||||||
publisher, err := amqpConnection.NewPublisher(context.TODO(), &rmq.QueueAddress{Queue: queueStream}, "stream-publisher")
|
publisher, err := amqpConnection.NewPublisher(context.TODO(), &rmq.QueueAddress{Queue: queueStream}, nil)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
// publish messages to the stream
|
// publish messages to the stream
|
||||||
|
|
@ -47,17 +48,14 @@ func main() {
|
||||||
switch publishResult.Outcome.(type) {
|
switch publishResult.Outcome.(type) {
|
||||||
case *rmq.StateAccepted:
|
case *rmq.StateAccepted:
|
||||||
rmq.Info("[Publisher]", "Message accepted", publishResult.Message.Data[0])
|
rmq.Info("[Publisher]", "Message accepted", publishResult.Message.Data[0])
|
||||||
break
|
|
||||||
case *rmq.StateReleased:
|
case *rmq.StateReleased:
|
||||||
rmq.Warn("[Publisher]", "Message was not routed", publishResult.Message.Data[0])
|
rmq.Warn("[Publisher]", "Message was not routed", publishResult.Message.Data[0])
|
||||||
break
|
|
||||||
case *rmq.StateRejected:
|
case *rmq.StateRejected:
|
||||||
rmq.Warn("[Publisher]", "Message rejected", publishResult.Message.Data[0])
|
rmq.Warn("[Publisher]", "Message rejected", publishResult.Message.Data[0])
|
||||||
stateType := publishResult.Outcome.(*rmq.StateRejected)
|
stateType := publishResult.Outcome.(*rmq.StateRejected)
|
||||||
if stateType.Error != nil {
|
if stateType.Error != nil {
|
||||||
rmq.Warn("[Publisher]", "Message rejected with error: %v", stateType.Error)
|
rmq.Warn("[Publisher]", "Message rejected with error: %v", stateType.Error)
|
||||||
}
|
}
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
||||||
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,10 @@ func checkError(err error) {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// see also: https://www.rabbitmq.com/blog/2024/12/13/amqp-filter-expressions
|
||||||
rmq.Info("Golang AMQP 1.0 Streams example with filtering")
|
rmq.Info("Golang AMQP 1.0 Streams example with filtering")
|
||||||
queueStream := "stream-go-queue-filtering-" + time.Now().String()
|
queueStream := "stream-go-queue-filtering-" + time.Now().String()
|
||||||
env := rmq.NewEnvironment([]string{"amqp://"}, nil)
|
env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
|
||||||
amqpConnection, err := env.NewConnection(context.Background())
|
amqpConnection, err := env.NewConnection(context.Background())
|
||||||
checkError(err)
|
checkError(err)
|
||||||
management := amqpConnection.Management()
|
management := amqpConnection.Management()
|
||||||
|
|
@ -35,7 +36,7 @@ func main() {
|
||||||
|
|
||||||
// create a stream publisher. In this case we use the QueueAddress to make the example
|
// create a stream publisher. In this case we use the QueueAddress to make the example
|
||||||
// simple. So we use the default exchange here.
|
// simple. So we use the default exchange here.
|
||||||
publisher, err := amqpConnection.NewPublisher(context.TODO(), &rmq.QueueAddress{Queue: queueStream}, "stream-publisher")
|
publisher, err := amqpConnection.NewPublisher(context.TODO(), &rmq.QueueAddress{Queue: queueStream}, nil)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
filters := []string{"MyFilter1", "MyFilter2", "MyFilter3", "MyFilter4"}
|
filters := []string{"MyFilter1", "MyFilter2", "MyFilter3", "MyFilter4"}
|
||||||
|
|
@ -50,17 +51,14 @@ func main() {
|
||||||
switch publishResult.Outcome.(type) {
|
switch publishResult.Outcome.(type) {
|
||||||
case *rmq.StateAccepted:
|
case *rmq.StateAccepted:
|
||||||
rmq.Info("[Publisher]", "Message accepted", publishResult.Message.Data[0])
|
rmq.Info("[Publisher]", "Message accepted", publishResult.Message.Data[0])
|
||||||
break
|
|
||||||
case *rmq.StateReleased:
|
case *rmq.StateReleased:
|
||||||
rmq.Warn("[Publisher]", "Message was not routed", publishResult.Message.Data[0])
|
rmq.Warn("[Publisher]", "Message was not routed", publishResult.Message.Data[0])
|
||||||
break
|
|
||||||
case *rmq.StateRejected:
|
case *rmq.StateRejected:
|
||||||
rmq.Warn("[Publisher]", "Message rejected", publishResult.Message.Data[0])
|
rmq.Warn("[Publisher]", "Message rejected", publishResult.Message.Data[0])
|
||||||
stateType := publishResult.Outcome.(*rmq.StateRejected)
|
stateType := publishResult.Outcome.(*rmq.StateRejected)
|
||||||
if stateType.Error != nil {
|
if stateType.Error != nil {
|
||||||
rmq.Warn("[Publisher]", "Message rejected with error: %v", stateType.Error)
|
rmq.Warn("[Publisher]", "Message rejected with error: %v", stateType.Error)
|
||||||
}
|
}
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
// these status are not supported. Leave it for AMQP 1.0 compatibility
|
||||||
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
// see: https://www.rabbitmq.com/docs/next/amqp#outcomes
|
||||||
|
|
@ -76,7 +74,25 @@ func main() {
|
||||||
|
|
||||||
// add a filter to the consumer, in this case we use only the filter values
|
// add a filter to the consumer, in this case we use only the filter values
|
||||||
// MyFilter1 and MyFilter2. So all other messages won't be received
|
// MyFilter1 and MyFilter2. So all other messages won't be received
|
||||||
Filters: []string{"MyFilter1", "MyFilter2"},
|
StreamFilterOptions: &rmq.StreamFilterOptions{
|
||||||
|
Values: []string{"MyFilter1", "MyFilter2"},
|
||||||
|
// it is also possible to filter by application properties or message properties
|
||||||
|
// you can create filters like:
|
||||||
|
// msg.ApplicationProperties = map[string]interface{}{"key3": "value3"}
|
||||||
|
// during the publish you can do something like:
|
||||||
|
// msg.ApplicationProperties = map[string]interface{}{"key1": "value1"}
|
||||||
|
// publisher.Publish(context.Background(), msg)
|
||||||
|
//ApplicationProperties: nil,
|
||||||
|
|
||||||
|
// or here you can filter by message properties
|
||||||
|
// like:
|
||||||
|
// msg.Properties = &amqp.MessageProperties{Subject: "MySubject"}
|
||||||
|
// during the publish you can do something like:
|
||||||
|
// msg.Properties = &amqp.MessageProperties{Subject: "MySubject"}
|
||||||
|
// publisher.Publish(context.Background(), msg)
|
||||||
|
//Properties: nil,
|
||||||
|
// see amqp_consumer_stream_test.go for more examples
|
||||||
|
},
|
||||||
})
|
})
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
|
rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func check(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// to run the example you can use the certificates from the rabbitmq-amqp-go-client
|
||||||
|
// inside the directory .ci/certs
|
||||||
|
caCert, err := os.ReadFile("/path/ca_certificate.pem")
|
||||||
|
check(err)
|
||||||
|
// Create a CA certificate pool and add the CA certificate to it
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
caCertPool.AppendCertsFromPEM(caCert)
|
||||||
|
|
||||||
|
// Load client cert
|
||||||
|
clientCert, err := tls.LoadX509KeyPair("/path//client_localhost_certificate.pem",
|
||||||
|
"/path//client_localhost_key.pem")
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// Create a TLS configuration
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{clientCert},
|
||||||
|
RootCAs: caCertPool,
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
ServerName: "localhost", // the server name should match the name on the certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
env := rmq.NewClusterEnvironment([]rmq.Endpoint{
|
||||||
|
{Address: "amqps://localhost:5671", Options: &rmq.AmqpConnOptions{
|
||||||
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
connection, err := env.NewConnection(context.Background())
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// Close the connection
|
||||||
|
err = connection.Close(context.Background())
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ func main() {
|
||||||
queueName := "getting-started-go-queue"
|
queueName := "getting-started-go-queue"
|
||||||
routingKey := "routing-key"
|
routingKey := "routing-key"
|
||||||
|
|
||||||
env := rmq.NewEnvironment([]string{"amqp://guest:guest@localhost:5672"}, nil)
|
env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
|
||||||
|
|
||||||
// Open a connection to the AMQP 1.0 server ( RabbitMQ >= 4.0)
|
// Open a connection to the AMQP 1.0 server ( RabbitMQ >= 4.0)
|
||||||
amqpConnection, err := env.NewConnection(context.Background())
|
amqpConnection, err := env.NewConnection(context.Background())
|
||||||
|
|
@ -60,7 +60,7 @@ func main() {
|
||||||
publisher, err := amqpConnection.NewPublisher(context.Background(), &rmq.ExchangeAddress{
|
publisher, err := amqpConnection.NewPublisher(context.Background(), &rmq.ExchangeAddress{
|
||||||
Exchange: exchangeName,
|
Exchange: exchangeName,
|
||||||
Key: routingKey,
|
Key: routingKey,
|
||||||
}, "getting-started-publisher")
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rmq.Error("Error creating publisher", err)
|
rmq.Error("Error creating publisher", err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
9
go.mod
9
go.mod
|
|
@ -1,9 +1,10 @@
|
||||||
module github.com/rabbitmq/rabbitmq-amqp-go-client
|
module github.com/rabbitmq/rabbitmq-amqp-go-client
|
||||||
|
|
||||||
go 1.22.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-amqp v1.4.0
|
github.com/Azure/go-amqp v1.4.0
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/onsi/ginkgo/v2 v2.22.1
|
github.com/onsi/ginkgo/v2 v2.22.1
|
||||||
github.com/onsi/gomega v1.36.2
|
github.com/onsi/gomega v1.36.2
|
||||||
|
|
@ -14,9 +15,9 @@ require (
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.38.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/tools v0.28.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
14
go.sum
14
go.sum
|
|
@ -8,6 +8,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||||
|
|
@ -22,12 +24,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TargetAddress is an interface that represents an address that can be used to send messages to.
|
// ITargetAddress is an interface that represents an address that can be used to send messages to.
|
||||||
// It can be either a Queue or an Exchange with a routing key.
|
// It can be either a Queue or an Exchange with a routing key.
|
||||||
type TargetAddress interface {
|
type ITargetAddress interface {
|
||||||
toAddress() (string, error)
|
toAddress() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ func (eas *ExchangeAddress) toAddress() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// address Creates the address for the exchange or queue following the RabbitMQ conventions.
|
// address Creates the address for the exchange or queue following the RabbitMQ conventions.
|
||||||
// see: https://www.rabbitmq.com/docs/next/amqp#address-v2
|
// see: https://www.rabbitmq.com/docs/amqp#address-v2
|
||||||
func address(exchange, key, queue *string, urlParameters *string) (string, error) {
|
func address(exchange, key, queue *string, urlParameters *string) (string, error) {
|
||||||
if exchange == nil && queue == nil {
|
if exchange == nil && queue == nil {
|
||||||
return "", errors.New("exchange or queue must be set")
|
return "", errors.New("exchange or queue must be set")
|
||||||
|
|
@ -61,9 +61,9 @@ func address(exchange, key, queue *string, urlParameters *string) (string, error
|
||||||
|
|
||||||
if !isStringNilOrEmpty(exchange) {
|
if !isStringNilOrEmpty(exchange) {
|
||||||
if !isStringNilOrEmpty(key) {
|
if !isStringNilOrEmpty(key) {
|
||||||
return "/" + exchanges + "/" + encodePathSegments(*exchange) + "/" + encodePathSegments(*key) + urlAppend, nil
|
return fmt.Sprintf("/%s/%s/%s%s", exchanges, encodePathSegments(*exchange), encodePathSegments(*key), urlAppend), nil
|
||||||
}
|
}
|
||||||
return "/" + exchanges + "/" + encodePathSegments(*exchange) + urlAppend, nil
|
return fmt.Sprintf("/%s/%s%s", exchanges, encodePathSegments(*exchange), urlAppend), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if queue == nil {
|
if queue == nil {
|
||||||
|
|
@ -73,8 +73,7 @@ func address(exchange, key, queue *string, urlParameters *string) (string, error
|
||||||
if isStringNilOrEmpty(queue) {
|
if isStringNilOrEmpty(queue) {
|
||||||
return "", errors.New("queue must be set")
|
return "", errors.New("queue must be set")
|
||||||
}
|
}
|
||||||
|
return fmt.Sprintf("/%s/%s%s", queues, encodePathSegments(*queue), urlAppend), nil
|
||||||
return "/" + queues + "/" + encodePathSegments(*queue) + urlAppend, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// exchangeAddress Creates the address for the exchange
|
// exchangeAddress Creates the address for the exchange
|
||||||
|
|
|
||||||
|
|
@ -6,59 +6,57 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("address builder test ", func() {
|
var _ = Describe("address builder test ", func() {
|
||||||
It("With exchange, queue and key should raise and error", func() {
|
// Error cases
|
||||||
queue := "my_queue"
|
Describe("Error cases", func() {
|
||||||
exchange := "my_exchange"
|
DescribeTable("should return appropriate errors",
|
||||||
|
func(exchange, key, queue *string, expectedErr string) {
|
||||||
_, err := address(&exchange, nil, &queue, nil)
|
_, err := address(exchange, key, queue, nil)
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(err.Error()).To(Equal("exchange and queue cannot be set together"))
|
Expect(err.Error()).To(Equal(expectedErr))
|
||||||
|
},
|
||||||
|
Entry("when both exchange and queue are set",
|
||||||
|
stringPtr("my_exchange"), nil, stringPtr("my_queue"),
|
||||||
|
"exchange and queue cannot be set together"),
|
||||||
|
Entry("when neither exchange nor queue is set",
|
||||||
|
nil, nil, nil,
|
||||||
|
"exchange or queue must be set"),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Without exchange and queue should raise and error", func() {
|
// Exchange-related cases
|
||||||
_, err := address(nil, nil, nil, nil)
|
Describe("Exchange addresses", func() {
|
||||||
Expect(err).NotTo(BeNil())
|
DescribeTable("should generate correct exchange addresses",
|
||||||
Expect(err.Error()).To(Equal("exchange or queue must be set"))
|
func(exchange, key *string, expected string) {
|
||||||
|
address, err := address(exchange, key, nil, nil)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(address).To(Equal(expected))
|
||||||
|
},
|
||||||
|
Entry("with exchange and key",
|
||||||
|
stringPtr("my_exchange"), stringPtr("my_key"),
|
||||||
|
"/exchanges/my_exchange/my_key"),
|
||||||
|
Entry("with exchange only",
|
||||||
|
stringPtr("my_exchange"), nil,
|
||||||
|
"/exchanges/my_exchange"),
|
||||||
|
Entry("with special characters",
|
||||||
|
stringPtr("my_ exchange/()"), stringPtr("my_key "),
|
||||||
|
"/exchanges/my_%20exchange%2F%28%29/my_key%20"),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("With exchange and key should return address", func() {
|
// Queue-related cases
|
||||||
exchange := "my_exchange"
|
Describe("Queue addresses", func() {
|
||||||
key := "my_key"
|
It("should generate correct queue address with special characters", func() {
|
||||||
|
queue := "my_queue>"
|
||||||
|
address, err := address(nil, nil, &queue, nil)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(address).To(Equal("/queues/my_queue%3E"))
|
||||||
|
})
|
||||||
|
|
||||||
address, err := address(&exchange, &key, nil, nil)
|
It("should generate correct purge queue address", func() {
|
||||||
Expect(err).To(BeNil())
|
queue := "my_queue"
|
||||||
Expect(address).To(Equal("/exchanges/my_exchange/my_key"))
|
address, err := purgeQueueAddress(&queue)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(address).To(Equal("/queues/my_queue/messages"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
It("With exchange should return address", func() {
|
|
||||||
exchange := "my_exchange"
|
|
||||||
address, err := address(&exchange, nil, nil, nil)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(address).To(Equal("/exchanges/my_exchange"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("With exchange and key with names to encode should return the encoded address", func() {
|
|
||||||
|
|
||||||
exchange := "my_ exchange/()"
|
|
||||||
key := "my_key "
|
|
||||||
|
|
||||||
address, err := address(&exchange, &key, nil, nil)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(address).To(Equal("/exchanges/my_%20exchange%2F%28%29/my_key%20"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("With queue should return address", func() {
|
|
||||||
queue := "my_queue>"
|
|
||||||
address, err := address(nil, nil, &queue, nil)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(address).To(Equal("/queues/my_queue%3E"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("With queue and urlParameters should return address", func() {
|
|
||||||
queue := "my_queue"
|
|
||||||
address, err := purgeQueueAddress(&queue)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(address).To(Equal("/queues/my_queue/messages"))
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,8 @@ func (b *AMQPBinding) Bind(ctx context.Context) (string, error) {
|
||||||
kv[destination] = b.destinationName
|
kv[destination] = b.destinationName
|
||||||
kv["arguments"] = make(map[string]any)
|
kv["arguments"] = make(map[string]any)
|
||||||
_, err := b.management.Request(ctx, kv, path, commandPost, []int{responseCode204})
|
_, err := b.management.Request(ctx, kv, path, commandPost, []int{responseCode204})
|
||||||
bindingPathWithExchangeQueueKey := bindingPathWithExchangeQueueKey(b.toQueue, b.sourceName, b.destinationName, b.bindingKey)
|
bindingPathWithExchangeQueueAndKey := bindingPathWithExchangeQueueKey(b.toQueue, b.sourceName, b.destinationName, b.bindingKey)
|
||||||
return bindingPathWithExchangeQueueKey, err
|
return bindingPathWithExchangeQueueAndKey, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unbind removes a binding between an exchange and a queue or exchange
|
// Unbind removes a binding between an exchange and a queue or exchange
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ var _ = Describe("AMQP Bindings test ", func() {
|
||||||
var connection *AmqpConnection
|
var connection *AmqpConnection
|
||||||
var management *AmqpManagement
|
var management *AmqpManagement
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
conn, err := Dial(context.TODO(), []string{"amqp://"}, nil)
|
conn, err := Dial(context.TODO(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
connection = conn
|
connection = conn
|
||||||
management = connection.Management()
|
management = connection.Management()
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,39 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//func (c *ConnUrlHelper) UseSsl(value bool) {
|
type AmqpAddress struct {
|
||||||
// c.UseSsl = value
|
// the address of the AMQP server
|
||||||
// if value {
|
// it is in the form of amqp://<host>:<port>
|
||||||
// c.Scheme = "amqps"
|
// or amqps://<host>:<port>
|
||||||
// } else {
|
// the port is optional
|
||||||
// c.Scheme = "amqp"
|
// the default port is 5672
|
||||||
// }
|
// the default protocol is amqp
|
||||||
//}
|
// the default host is localhost
|
||||||
|
// the default virtual host is "/"
|
||||||
|
// the default user is guest
|
||||||
|
// the default password is guest
|
||||||
|
// the default SASL type is SASLTypeAnonymous
|
||||||
|
Address string
|
||||||
|
// Options: Additional options for the connection
|
||||||
|
Options *AmqpConnOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type OAuth2Options struct {
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OAuth2Options) Clone() *OAuth2Options {
|
||||||
|
cloned := &OAuth2Options{
|
||||||
|
Token: o.Token,
|
||||||
|
}
|
||||||
|
return cloned
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
type AmqpConnOptions struct {
|
type AmqpConnOptions struct {
|
||||||
// wrapper for amqp.ConnOptions
|
// wrapper for amqp.ConnOptions
|
||||||
ContainerID string
|
ContainerID string
|
||||||
|
|
||||||
// wrapper for amqp.ConnOptions
|
// wrapper for amqp.ConnOptions
|
||||||
HostName string
|
HostName string
|
||||||
// wrapper for amqp.ConnOptions
|
// wrapper for amqp.ConnOptions
|
||||||
|
|
@ -51,8 +72,40 @@ type AmqpConnOptions struct {
|
||||||
// when the connection is closed unexpectedly.
|
// when the connection is closed unexpectedly.
|
||||||
RecoveryConfiguration *RecoveryConfiguration
|
RecoveryConfiguration *RecoveryConfiguration
|
||||||
|
|
||||||
// copy the addresses for reconnection
|
// The OAuth2Options is used to configure the connection with OAuth2 token.
|
||||||
addresses []string
|
OAuth2Options *OAuth2Options
|
||||||
|
|
||||||
|
// Local connection identifier (not sent to the server)
|
||||||
|
// if not provided, a random UUID is generated
|
||||||
|
Id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AmqpConnOptions) isOAuth2() bool {
|
||||||
|
return a.OAuth2Options != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AmqpConnOptions) Clone() *AmqpConnOptions {
|
||||||
|
|
||||||
|
cloned := &AmqpConnOptions{
|
||||||
|
ContainerID: a.ContainerID,
|
||||||
|
IdleTimeout: a.IdleTimeout,
|
||||||
|
MaxFrameSize: a.MaxFrameSize,
|
||||||
|
MaxSessions: a.MaxSessions,
|
||||||
|
Properties: a.Properties,
|
||||||
|
SASLType: a.SASLType,
|
||||||
|
TLSConfig: a.TLSConfig,
|
||||||
|
WriteTimeout: a.WriteTimeout,
|
||||||
|
Id: a.Id,
|
||||||
|
}
|
||||||
|
if a.OAuth2Options != nil {
|
||||||
|
cloned.OAuth2Options = a.OAuth2Options.Clone()
|
||||||
|
}
|
||||||
|
if a.RecoveryConfiguration != nil {
|
||||||
|
cloned.RecoveryConfiguration = a.RecoveryConfiguration.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloned
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AmqpConnection struct {
|
type AmqpConnection struct {
|
||||||
|
|
@ -60,10 +113,10 @@ type AmqpConnection struct {
|
||||||
featuresAvailable *featuresAvailable
|
featuresAvailable *featuresAvailable
|
||||||
|
|
||||||
azureConnection *amqp.Conn
|
azureConnection *amqp.Conn
|
||||||
id string
|
|
||||||
management *AmqpManagement
|
management *AmqpManagement
|
||||||
lifeCycle *LifeCycle
|
lifeCycle *LifeCycle
|
||||||
amqpConnOptions *AmqpConnOptions
|
amqpConnOptions *AmqpConnOptions
|
||||||
|
address string
|
||||||
session *amqp.Session
|
session *amqp.Session
|
||||||
refMap *sync.Map
|
refMap *sync.Map
|
||||||
entitiesTracker *entitiesTracker
|
entitiesTracker *entitiesTracker
|
||||||
|
|
@ -75,9 +128,10 @@ func (a *AmqpConnection) Properties() map[string]any {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublisher creates a new Publisher that sends messages to the provided destination.
|
// NewPublisher creates a new Publisher that sends messages to the provided destination.
|
||||||
// The destination is a TargetAddress that can be a Queue or an Exchange with a routing key.
|
// The destination is a ITargetAddress that can be a Queue or an Exchange with a routing key.
|
||||||
|
// options is an IPublisherOptions that can be used to configure the publisher.
|
||||||
// See QueueAddress and ExchangeAddress for more information.
|
// See QueueAddress and ExchangeAddress for more information.
|
||||||
func (a *AmqpConnection) NewPublisher(ctx context.Context, destination TargetAddress, linkName string) (*Publisher, error) {
|
func (a *AmqpConnection) NewPublisher(ctx context.Context, destination ITargetAddress, options IPublisherOptions) (*Publisher, error) {
|
||||||
destinationAdd := ""
|
destinationAdd := ""
|
||||||
err := error(nil)
|
err := error(nil)
|
||||||
if destination != nil {
|
if destination != nil {
|
||||||
|
|
@ -91,14 +145,20 @@ func (a *AmqpConnection) NewPublisher(ctx context.Context, destination TargetAdd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPublisher(ctx, a, destinationAdd, linkName)
|
return newPublisher(ctx, a, destinationAdd, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConsumer creates a new Consumer that listens to the provided destination. Destination is a QueueAddress.
|
// NewConsumer creates a new Consumer that listens to the provided Queue
|
||||||
func (a *AmqpConnection) NewConsumer(ctx context.Context, queueName string, options ConsumerOptions) (*Consumer, error) {
|
func (a *AmqpConnection) NewConsumer(ctx context.Context, queueName string, options IConsumerOptions) (*Consumer, error) {
|
||||||
destination := &QueueAddress{
|
destination := &QueueAddress{
|
||||||
Queue: queueName,
|
Queue: queueName,
|
||||||
}
|
}
|
||||||
|
if options != nil {
|
||||||
|
err := options.validate(a.featuresAvailable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
destinationAdd, err := destination.toAddress()
|
destinationAdd, err := destination.toAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -110,16 +170,53 @@ func (a *AmqpConnection) NewConsumer(ctx context.Context, queueName string, opti
|
||||||
|
|
||||||
// Dial connect to the AMQP 1.0 server using the provided connectionSettings
|
// Dial connect to the AMQP 1.0 server using the provided connectionSettings
|
||||||
// Returns a pointer to the new AmqpConnection if successful else an error.
|
// Returns a pointer to the new AmqpConnection if successful else an error.
|
||||||
// addresses is a list of addresses to connect to. It picks one randomly.
|
func Dial(ctx context.Context, address string, connOptions *AmqpConnOptions) (*AmqpConnection, error) {
|
||||||
// It is enough that one of the addresses is reachable.
|
connOptions, err := validateOptions(connOptions)
|
||||||
func Dial(ctx context.Context, addresses []string, connOptions *AmqpConnOptions, args ...string) (*AmqpConnection, error) {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the connection
|
||||||
|
conn := &AmqpConnection{
|
||||||
|
management: newAmqpManagement(),
|
||||||
|
lifeCycle: NewLifeCycle(),
|
||||||
|
amqpConnOptions: connOptions,
|
||||||
|
entitiesTracker: newEntitiesTracker(),
|
||||||
|
featuresAvailable: newFeaturesAvailable(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.open(ctx, address, connOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn.amqpConnOptions = connOptions
|
||||||
|
conn.address = address
|
||||||
|
conn.lifeCycle.SetState(&StateOpen{})
|
||||||
|
return conn, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOptions(connOptions *AmqpConnOptions) (*AmqpConnOptions, error) {
|
||||||
if connOptions == nil {
|
if connOptions == nil {
|
||||||
connOptions = &AmqpConnOptions{
|
connOptions = &AmqpConnOptions{}
|
||||||
// RabbitMQ requires SASL security layer
|
}
|
||||||
// to be enabled for AMQP 1.0 connections.
|
if connOptions.SASLType == nil {
|
||||||
// So this is mandatory and default in case not defined.
|
// RabbitMQ requires SASL security layer
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
// to be enabled for AMQP 1.0 connections.
|
||||||
|
// So this is mandatory and default in case not defined.
|
||||||
|
connOptions.SASLType = amqp.SASLTypeAnonymous()
|
||||||
|
}
|
||||||
|
|
||||||
|
if connOptions.Id == "" {
|
||||||
|
connOptions.Id = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of OAuth2 token, the SASLType should be set to SASLTypePlain
|
||||||
|
if connOptions.isOAuth2() {
|
||||||
|
if connOptions.OAuth2Options.Token == "" {
|
||||||
|
return nil, fmt.Errorf("OAuth2 token is empty")
|
||||||
}
|
}
|
||||||
|
connOptions.SASLType = amqp.SASLTypePlain("", connOptions.OAuth2Options.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if connOptions.RecoveryConfiguration == nil {
|
if connOptions.RecoveryConfiguration == nil {
|
||||||
|
|
@ -134,37 +231,28 @@ func Dial(ctx context.Context, addresses []string, connOptions *AmqpConnOptions,
|
||||||
return nil, fmt.Errorf("BackOffReconnectInterval should be greater than 1 second")
|
return nil, fmt.Errorf("BackOffReconnectInterval should be greater than 1 second")
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the connection
|
return connOptions, nil
|
||||||
|
|
||||||
conn := &AmqpConnection{
|
|
||||||
management: NewAmqpManagement(),
|
|
||||||
lifeCycle: NewLifeCycle(),
|
|
||||||
amqpConnOptions: connOptions,
|
|
||||||
entitiesTracker: newEntitiesTracker(),
|
|
||||||
featuresAvailable: newFeaturesAvailable(),
|
|
||||||
}
|
|
||||||
tmp := make([]string, len(addresses))
|
|
||||||
copy(tmp, addresses)
|
|
||||||
|
|
||||||
err := conn.open(ctx, addresses, connOptions, args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn.amqpConnOptions = connOptions
|
|
||||||
conn.amqpConnOptions.addresses = addresses
|
|
||||||
conn.lifeCycle.SetState(&StateOpen{})
|
|
||||||
return conn, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a connection to the AMQP 1.0 server.
|
// Open opens a connection to the AMQP 1.0 server.
|
||||||
// using the provided connectionSettings and the AMQPLite library.
|
// using the provided connectionSettings and the AMQPLite library.
|
||||||
// Setups the connection and the management interface.
|
// Setups the connection and the management interface.
|
||||||
func (a *AmqpConnection) open(ctx context.Context, addresses []string, connOptions *AmqpConnOptions, args ...string) error {
|
func (a *AmqpConnection) open(ctx context.Context, address string, connOptions *AmqpConnOptions) error {
|
||||||
|
|
||||||
|
// random pick and extract one address to use for connection
|
||||||
|
var azureConnection *amqp.Conn
|
||||||
|
//connOptions.hostName is the way to set the virtual host
|
||||||
|
// so we need to pre-parse the URI to get the virtual host
|
||||||
|
// the PARSE is copied from go-amqp091 library
|
||||||
|
// the URI will be parsed is parsed again in the amqp lite library
|
||||||
|
uri, err := ParseURI(address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
amqpLiteConnOptions := &amqp.ConnOptions{
|
amqpLiteConnOptions := &amqp.ConnOptions{
|
||||||
ContainerID: connOptions.ContainerID,
|
ContainerID: connOptions.ContainerID,
|
||||||
HostName: connOptions.HostName,
|
HostName: fmt.Sprintf("vhost:%s", uri.Vhost),
|
||||||
IdleTimeout: connOptions.IdleTimeout,
|
IdleTimeout: connOptions.IdleTimeout,
|
||||||
MaxFrameSize: connOptions.MaxFrameSize,
|
MaxFrameSize: connOptions.MaxFrameSize,
|
||||||
MaxSessions: connOptions.MaxSessions,
|
MaxSessions: connOptions.MaxSessions,
|
||||||
|
|
@ -173,155 +261,134 @@ func (a *AmqpConnection) open(ctx context.Context, addresses []string, connOptio
|
||||||
TLSConfig: connOptions.TLSConfig,
|
TLSConfig: connOptions.TLSConfig,
|
||||||
WriteTimeout: connOptions.WriteTimeout,
|
WriteTimeout: connOptions.WriteTimeout,
|
||||||
}
|
}
|
||||||
tmp := make([]string, len(addresses))
|
azureConnection, err = amqp.Dial(ctx, address, amqpLiteConnOptions)
|
||||||
copy(tmp, addresses)
|
if err != nil {
|
||||||
|
Error("Failed to open connection", ExtractWithoutPassword(address), err, "ID", connOptions.Id)
|
||||||
// random pick and extract one address to use for connection
|
return fmt.Errorf("failed to open connection: %w", err)
|
||||||
var azureConnection *amqp.Conn
|
|
||||||
for len(tmp) > 0 {
|
|
||||||
idx := random(len(tmp))
|
|
||||||
addr := tmp[idx]
|
|
||||||
//connOptions.HostName is the way to set the virtual host
|
|
||||||
// so we need to pre-parse the URI to get the virtual host
|
|
||||||
// the PARSE is copied from go-amqp091 library
|
|
||||||
// the URI will be parsed is parsed again in the amqp lite library
|
|
||||||
uri, err := ParseURI(addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
connOptions.HostName = fmt.Sprintf("vhost:%s", uri.Vhost)
|
|
||||||
// remove the index from the tmp list
|
|
||||||
tmp = append(tmp[:idx], tmp[idx+1:]...)
|
|
||||||
azureConnection, err = amqp.Dial(ctx, addr, amqpLiteConnOptions)
|
|
||||||
if err != nil {
|
|
||||||
Error("Failed to open connection", ExtractWithoutPassword(addr), err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
a.properties = azureConnection.Properties()
|
|
||||||
err = a.featuresAvailable.ParseProperties(a.properties)
|
|
||||||
if err != nil {
|
|
||||||
Warn("Validate properties Error.", ExtractWithoutPassword(addr), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.featuresAvailable.is4OrMore {
|
|
||||||
Warn("The server version is less than 4.0.0", ExtractWithoutPassword(addr))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.featuresAvailable.isRabbitMQ {
|
|
||||||
Warn("The server is not RabbitMQ", ExtractWithoutPassword(addr))
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug("Connected to", ExtractWithoutPassword(addr))
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if azureConnection == nil {
|
a.properties = azureConnection.Properties()
|
||||||
return fmt.Errorf("failed to connect to any of the provided addresses")
|
err = a.featuresAvailable.ParseProperties(a.properties)
|
||||||
|
if err != nil {
|
||||||
|
Warn("Validate properties Error.", ExtractWithoutPassword(address), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if !a.featuresAvailable.is4OrMore {
|
||||||
a.id = args[0]
|
Warn("The server version is less than 4.0.0", ExtractWithoutPassword(address), "ID", connOptions.Id)
|
||||||
} else {
|
|
||||||
a.id = uuid.New().String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !a.featuresAvailable.isRabbitMQ {
|
||||||
|
Warn("The server is not RabbitMQ", ExtractWithoutPassword(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug("Connected to", ExtractWithoutPassword(address), "ID", connOptions.Id)
|
||||||
a.azureConnection = azureConnection
|
a.azureConnection = azureConnection
|
||||||
var err error
|
|
||||||
a.session, err = a.azureConnection.NewSession(ctx, nil)
|
a.session, err = a.azureConnection.NewSession(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open session, for the connection id:%s, error: %w", a.Id(), err)
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
<-azureConnection.Done()
|
||||||
case <-azureConnection.Done():
|
{
|
||||||
{
|
a.lifeCycle.SetState(&StateClosed{error: azureConnection.Err()})
|
||||||
a.lifeCycle.SetState(&StateClosed{error: azureConnection.Err()})
|
if azureConnection.Err() != nil {
|
||||||
if azureConnection.Err() != nil {
|
Error("connection closed unexpectedly", "error", azureConnection.Err(), "ID", a.Id())
|
||||||
Error("connection closed unexpectedly", "error", azureConnection.Err())
|
a.maybeReconnect()
|
||||||
a.maybeReconnect()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
Debug("connection closed successfully")
|
|
||||||
}
|
}
|
||||||
|
Debug("connection closed successfully", "ID", a.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = a.management.Open(ctx, a)
|
err = a.management.Open(ctx, a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO close connection?
|
// TODO close connection?
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Debug("Management interface opened", "ID", a.Id())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (a *AmqpConnection) maybeReconnect() {
|
func (a *AmqpConnection) maybeReconnect() {
|
||||||
|
|
||||||
if !a.amqpConnOptions.RecoveryConfiguration.ActiveRecovery {
|
if !a.amqpConnOptions.RecoveryConfiguration.ActiveRecovery {
|
||||||
Info("Recovery is disabled, closing connection")
|
Info("Recovery is disabled, closing connection", "ID", a.Id())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.lifeCycle.SetState(&StateReconnecting{})
|
a.lifeCycle.SetState(&StateReconnecting{})
|
||||||
numberOfAttempts := 1
|
// Add exponential backoff with jitter
|
||||||
waitTime := a.amqpConnOptions.RecoveryConfiguration.BackOffReconnectInterval
|
baseDelay := a.amqpConnOptions.RecoveryConfiguration.BackOffReconnectInterval
|
||||||
reconnected := false
|
maxDelay := 1 * time.Minute
|
||||||
for numberOfAttempts <= a.amqpConnOptions.RecoveryConfiguration.MaxReconnectAttempts {
|
|
||||||
|
for attempt := 1; attempt <= a.amqpConnOptions.RecoveryConfiguration.MaxReconnectAttempts; attempt++ {
|
||||||
|
|
||||||
///wait for before reconnecting
|
///wait for before reconnecting
|
||||||
// add some random milliseconds to the wait time to avoid thundering herd
|
// add some random milliseconds to the wait time to avoid thundering herd
|
||||||
// the random time is between 0 and 500 milliseconds
|
// the random time is between 0 and 500 milliseconds
|
||||||
waitTime = waitTime + time.Duration(rand.Intn(500))*time.Millisecond
|
// Calculate delay with exponential backoff and jitter
|
||||||
|
jitter := time.Duration(rand.Intn(500)) * time.Millisecond
|
||||||
|
delay := baseDelay + jitter
|
||||||
|
if delay > maxDelay {
|
||||||
|
delay = maxDelay
|
||||||
|
}
|
||||||
|
|
||||||
Info("Waiting before reconnecting", "in", waitTime, "attempt", numberOfAttempts)
|
Info("Attempting reconnection", "attempt", attempt, "delay", delay, "ID", a.Id())
|
||||||
time.Sleep(waitTime)
|
time.Sleep(delay)
|
||||||
// context with timeout
|
// context with timeout
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
// try to createSender
|
// try to createSender
|
||||||
err := a.open(ctx, a.amqpConnOptions.addresses, a.amqpConnOptions)
|
err := a.open(ctx, a.address, a.amqpConnOptions)
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
if err != nil {
|
if err == nil {
|
||||||
numberOfAttempts++
|
a.restartEntities()
|
||||||
waitTime = waitTime * 2
|
a.lifeCycle.SetState(&StateOpen{})
|
||||||
Error("Failed to connection. ", "id", a.Id(), "error", err)
|
return
|
||||||
} else {
|
|
||||||
reconnected = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
baseDelay *= 2
|
||||||
|
Error("Reconnection attempt failed", "attempt", attempt, "error", err, "ID", a.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
if reconnected {
|
// If we reach here, all attempts failed
|
||||||
var fails int32
|
Error("All reconnection attempts failed, closing connection", "ID", a.Id())
|
||||||
Info("Reconnected successfully, restarting publishers and consumers")
|
a.lifeCycle.SetState(&StateClosed{error: ErrMaxReconnectAttemptsReached})
|
||||||
a.entitiesTracker.publishers.Range(func(key, value any) bool {
|
|
||||||
publisher := value.(*Publisher)
|
|
||||||
// try to createSender
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
||||||
err := publisher.createSender(ctx)
|
|
||||||
if err != nil {
|
|
||||||
atomic.AddInt32(&fails, 1)
|
|
||||||
Error("Failed to createSender publisher", "ID", publisher.Id(), "error", err)
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
Info("Restarted publishers", "number of fails", fails)
|
|
||||||
fails = 0
|
|
||||||
a.entitiesTracker.consumers.Range(func(key, value any) bool {
|
|
||||||
consumer := value.(*Consumer)
|
|
||||||
// try to createSender
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
||||||
err := consumer.createReceiver(ctx)
|
|
||||||
if err != nil {
|
|
||||||
atomic.AddInt32(&fails, 1)
|
|
||||||
Error("Failed to createReceiver consumer", "ID", consumer.Id(), "error", err)
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
Info("Restarted consumers", "number of fails", fails)
|
|
||||||
|
|
||||||
a.lifeCycle.SetState(&StateOpen{})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// restartEntities attempts to restart all publishers and consumers after a reconnection
|
||||||
|
func (a *AmqpConnection) restartEntities() {
|
||||||
|
var publisherFails, consumerFails int32
|
||||||
|
|
||||||
|
// Restart publishers
|
||||||
|
a.entitiesTracker.publishers.Range(func(key, value any) bool {
|
||||||
|
publisher := value.(*Publisher)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := publisher.createSender(ctx); err != nil {
|
||||||
|
atomic.AddInt32(&publisherFails, 1)
|
||||||
|
Error("Failed to restart publisher", "ID", publisher.Id(), "error", err, "ID", a.Id())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Restart consumers
|
||||||
|
a.entitiesTracker.consumers.Range(func(key, value any) bool {
|
||||||
|
consumer := value.(*Consumer)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := consumer.createReceiver(ctx); err != nil {
|
||||||
|
atomic.AddInt32(&consumerFails, 1)
|
||||||
|
Error("Failed to restart consumer", "ID", consumer.Id(), "error", err, "ID", a.Id())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
Info("Entity restart complete",
|
||||||
|
"publisherFails", publisherFails,
|
||||||
|
"consumerFails", consumerFails)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) close() {
|
func (a *AmqpConnection) close() {
|
||||||
|
|
@ -343,7 +410,7 @@ func (a *AmqpConnection) Close(ctx context.Context) error {
|
||||||
|
|
||||||
err := a.management.Close(ctx)
|
err := a.management.Close(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Failed to close management", "error:", err)
|
Error("Failed to close management", "error:", err, "ID", a.Id())
|
||||||
}
|
}
|
||||||
err = a.azureConnection.Close()
|
err = a.azureConnection.Close()
|
||||||
a.close()
|
a.close()
|
||||||
|
|
@ -353,15 +420,15 @@ func (a *AmqpConnection) Close(ctx context.Context) error {
|
||||||
// NotifyStatusChange registers a channel to receive getState change notifications
|
// NotifyStatusChange registers a channel to receive getState change notifications
|
||||||
// from the connection.
|
// from the connection.
|
||||||
func (a *AmqpConnection) NotifyStatusChange(channel chan *StateChanged) {
|
func (a *AmqpConnection) NotifyStatusChange(channel chan *StateChanged) {
|
||||||
a.lifeCycle.chStatusChanged = channel
|
a.lifeCycle.notifyStatusChange(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) State() LifeCycleState {
|
func (a *AmqpConnection) State() ILifeCycleState {
|
||||||
return a.lifeCycle.State()
|
return a.lifeCycle.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) Id() string {
|
func (a *AmqpConnection) Id() string {
|
||||||
return a.id
|
return a.amqpConnOptions.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
// *** management section ***
|
// *** management section ***
|
||||||
|
|
@ -372,4 +439,24 @@ func (a *AmqpConnection) Management() *AmqpManagement {
|
||||||
return a.management
|
return a.management
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AmqpConnection) RefreshToken(background context.Context, token string) error {
|
||||||
|
if !a.amqpConnOptions.isOAuth2() {
|
||||||
|
return fmt.Errorf("the connection is not configured to use OAuth2 token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.amqpConnOptions.isOAuth2() && !a.featuresAvailable.is41OrMore {
|
||||||
|
return fmt.Errorf("the server does not support OAuth2 token, you need to upgrade to RabbitMQ 4.1 or later")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.Management().refreshToken(background, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// update the SASLType in case of reconnect after token refresh
|
||||||
|
// it should use the new token
|
||||||
|
a.amqpConnOptions.SASLType = amqp.SASLTypePlain("", token)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//*** end management section ***
|
//*** end management section ***
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
package rabbitmqamqp
|
package rabbitmqamqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrMaxReconnectAttemptsReached typed error when the MaxReconnectAttempts is reached
|
||||||
|
var ErrMaxReconnectAttemptsReached = errors.New("max reconnect attempts reached, connection will not be recovered")
|
||||||
|
|
||||||
type RecoveryConfiguration struct {
|
type RecoveryConfiguration struct {
|
||||||
/*
|
/*
|
||||||
ActiveRecovery Define if the recovery is activated.
|
ActiveRecovery Define if the recovery is activated.
|
||||||
|
|
@ -29,6 +33,17 @@ type RecoveryConfiguration struct {
|
||||||
MaxReconnectAttempts int
|
MaxReconnectAttempts int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RecoveryConfiguration) Clone() *RecoveryConfiguration {
|
||||||
|
cloned := &RecoveryConfiguration{
|
||||||
|
ActiveRecovery: c.ActiveRecovery,
|
||||||
|
BackOffReconnectInterval: c.BackOffReconnectInterval,
|
||||||
|
MaxReconnectAttempts: c.MaxReconnectAttempts,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloned
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func NewRecoveryConfiguration() *RecoveryConfiguration {
|
func NewRecoveryConfiguration() *RecoveryConfiguration {
|
||||||
return &RecoveryConfiguration{
|
return &RecoveryConfiguration{
|
||||||
ActiveRecovery: true,
|
ActiveRecovery: true,
|
||||||
|
|
@ -49,35 +64,19 @@ func newEntitiesTracker() *entitiesTracker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entitiesTracker) storeOrReplaceProducer(entity entityIdentifier) {
|
func (e *entitiesTracker) storeOrReplaceProducer(entity iEntityIdentifier) {
|
||||||
e.publishers.Store(entity.Id(), entity)
|
e.publishers.Store(entity.Id(), entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entitiesTracker) getProducer(id string) (*Publisher, bool) {
|
func (e *entitiesTracker) removeProducer(entity iEntityIdentifier) {
|
||||||
producer, ok := e.publishers.Load(id)
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return producer.(*Publisher), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entitiesTracker) removeProducer(entity entityIdentifier) {
|
|
||||||
e.publishers.Delete(entity.Id())
|
e.publishers.Delete(entity.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entitiesTracker) storeOrReplaceConsumer(entity entityIdentifier) {
|
func (e *entitiesTracker) storeOrReplaceConsumer(entity iEntityIdentifier) {
|
||||||
e.consumers.Store(entity.Id(), entity)
|
e.consumers.Store(entity.Id(), entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entitiesTracker) getConsumer(id string) (*Consumer, bool) {
|
func (e *entitiesTracker) removeConsumer(entity iEntityIdentifier) {
|
||||||
consumer, ok := e.consumers.Load(id)
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return consumer.(*Consumer), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entitiesTracker) removeConsumer(entity entityIdentifier) {
|
|
||||||
e.consumers.Delete(entity.Id())
|
e.consumers.Delete(entity.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package rabbitmqamqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
testhelper "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/test-helper"
|
testhelper "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/test-helper"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Recovery connection test", func() {
|
var _ = Describe("Recovery connection test", func() {
|
||||||
|
|
@ -22,7 +23,7 @@ var _ = Describe("Recovery connection test", func() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
name := "connection should reconnect producers and consumers if dropped by via REST API"
|
name := "connection should reconnect producers and consumers if dropped by via REST API"
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
env := NewEnvironment("amqp://", &AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
ContainerID: name,
|
ContainerID: name,
|
||||||
// reduced the reconnect interval to speed up the test
|
// reduced the reconnect interval to speed up the test
|
||||||
|
|
@ -31,7 +32,10 @@ var _ = Describe("Recovery connection test", func() {
|
||||||
BackOffReconnectInterval: 2 * time.Second,
|
BackOffReconnectInterval: 2 * time.Second,
|
||||||
MaxReconnectAttempts: 5,
|
MaxReconnectAttempts: 5,
|
||||||
},
|
},
|
||||||
|
Id: "reconnect producers and consumers",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
connection, err := env.NewConnection(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
ch := make(chan *StateChanged, 1)
|
ch := make(chan *StateChanged, 1)
|
||||||
connection.NotifyStatusChange(ch)
|
connection.NotifyStatusChange(ch)
|
||||||
|
|
@ -45,10 +49,11 @@ var _ = Describe("Recovery connection test", func() {
|
||||||
|
|
||||||
consumer, err := connection.NewConsumer(context.Background(),
|
consumer, err := connection.NewConsumer(context.Background(),
|
||||||
qName, nil)
|
qName, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{
|
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{
|
||||||
Queue: qName,
|
Queue: qName,
|
||||||
}, "test")
|
}, nil)
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
|
@ -113,11 +118,28 @@ var _ = Describe("Recovery connection test", func() {
|
||||||
// from reconnecting to open
|
// from reconnecting to open
|
||||||
// from open to closed (without error)
|
// from open to closed (without error)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
Expect(consumer.Close(context.Background())).NotTo(BeNil())
|
||||||
|
Expect(publisher.Close(context.Background())).NotTo(BeNil())
|
||||||
|
|
||||||
|
entLen := 0
|
||||||
|
connection.entitiesTracker.consumers.Range(func(key, value interface{}) bool {
|
||||||
|
entLen++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
Expect(entLen).To(Equal(0))
|
||||||
|
|
||||||
|
entLen = 0
|
||||||
|
connection.entitiesTracker.publishers.Range(func(key, value interface{}) bool {
|
||||||
|
entLen++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
Expect(entLen).To(Equal(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("connection should not reconnect producers and consumers if the auto-recovery is disabled", func() {
|
It("connection should not reconnect producers and consumers if the auto-recovery is disabled", func() {
|
||||||
name := "connection should reconnect producers and consumers if dropped by via REST API"
|
name := "connection should reconnect producers and consumers if dropped by via REST API"
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
connection, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
ContainerID: name,
|
ContainerID: name,
|
||||||
// reduced the reconnect interval to speed up the test
|
// reduced the reconnect interval to speed up the test
|
||||||
|
|
@ -155,7 +177,7 @@ var _ = Describe("Recovery connection test", func() {
|
||||||
|
|
||||||
It("validate the Recovery connection parameters", func() {
|
It("validate the Recovery connection parameters", func() {
|
||||||
|
|
||||||
_, err := Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
_, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
// reduced the reconnect interval to speed up the test
|
// reduced the reconnect interval to speed up the test
|
||||||
RecoveryConfiguration: &RecoveryConfiguration{
|
RecoveryConfiguration: &RecoveryConfiguration{
|
||||||
|
|
@ -167,7 +189,7 @@ var _ = Describe("Recovery connection test", func() {
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
Expect(err.Error()).To(ContainSubstring("BackOffReconnectInterval should be greater than"))
|
Expect(err.Error()).To(ContainSubstring("BackOffReconnectInterval should be greater than"))
|
||||||
|
|
||||||
_, err = Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
_, err = Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
RecoveryConfiguration: &RecoveryConfiguration{
|
RecoveryConfiguration: &RecoveryConfiguration{
|
||||||
ActiveRecovery: true,
|
ActiveRecovery: true,
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,29 @@ package rabbitmqamqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("AMQP connection Test", func() {
|
var _ = Describe("AMQP connection Test", func() {
|
||||||
It("AMQP SASLTypeAnonymous connection should succeed", func() {
|
It("AMQP SASLTypeAnonymous connection should succeed", func() {
|
||||||
|
connection, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
|
||||||
SASLType: amqp.SASLTypeAnonymous()})
|
SASLType: amqp.SASLTypeAnonymous()})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
err = connection.Close(context.Background())
|
err = connection.Close(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
})
|
})
|
||||||
|
//
|
||||||
It("AMQP SASLTypePlain connection should succeed", func() {
|
It("AMQP SASLTypePlain connection should succeed", func() {
|
||||||
|
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
connection, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypePlain("guest", "guest")})
|
SASLType: amqp.SASLTypePlain("guest", "guest")})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -29,38 +33,17 @@ var _ = Describe("AMQP connection Test", func() {
|
||||||
err = connection.Close(context.Background())
|
err = connection.Close(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
})
|
})
|
||||||
|
//
|
||||||
It("AMQP connection connect to the one correct uri and fails the others", func() {
|
|
||||||
conn, err := Dial(context.Background(), []string{"amqp://localhost:1234", "amqp://nohost:555", "amqp://"}, nil)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(conn.Close(context.Background()))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("AMQP connection should fail due of wrong Port", func() {
|
|
||||||
_, err := Dial(context.Background(), []string{"amqp://localhost:1234"}, nil)
|
|
||||||
Expect(err).NotTo(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("AMQP connection should fail due of wrong Host", func() {
|
|
||||||
_, err := Dial(context.Background(), []string{"amqp://wrong_host:5672"}, nil)
|
|
||||||
Expect(err).NotTo(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("AMQP connection should fails with all the wrong uris", func() {
|
|
||||||
_, err := Dial(context.Background(), []string{"amqp://localhost:1234", "amqp://nohost:555", "amqp://nono"}, nil)
|
|
||||||
Expect(err).NotTo(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("AMQP connection should fail due to context cancellation", func() {
|
It("AMQP connection should fail due to context cancellation", func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||||
cancel()
|
cancel()
|
||||||
_, err := Dial(ctx, []string{"amqp://"}, nil)
|
_, err := Dial(ctx, "amqp://", nil)
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
})
|
})
|
||||||
|
//
|
||||||
It("AMQP connection should receive events", func() {
|
It("AMQP connection should receive events", func() {
|
||||||
ch := make(chan *StateChanged, 1)
|
ch := make(chan *StateChanged, 1)
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
connection.NotifyStatusChange(ch)
|
connection.NotifyStatusChange(ch)
|
||||||
err = connection.Close(context.Background())
|
err = connection.Close(context.Background())
|
||||||
|
|
@ -72,19 +55,113 @@ var _ = Describe("AMQP connection Test", func() {
|
||||||
Expect(recv.To).To(Equal(&StateClosed{}))
|
Expect(recv.To).To(Equal(&StateClosed{}))
|
||||||
})
|
})
|
||||||
|
|
||||||
//It("AMQP TLS connection should success with SASLTypeAnonymous ", func() {
|
It("Entity tracker should be aligned with consumers and publishers ", func() {
|
||||||
// amqpConnection := NewAmqpConnection()
|
connection, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
// Expect(amqpConnection).NotTo(BeNil())
|
SASLType: amqp.SASLTypeAnonymous()})
|
||||||
// Expect(amqpConnection).To(BeAssignableToTypeOf(&AmqpConnection{}))
|
Expect(err).To(BeNil())
|
||||||
//
|
Expect(connection).NotTo(BeNil())
|
||||||
// connectionSettings := NewConnUrlHelper().
|
|
||||||
// UseSsl(true).Port(5671).TlsConfig(&tls.Config{
|
queueName := generateNameWithDateTime("Entity tracker should be aligned with consumers and publishers")
|
||||||
// //ServerName: "localhost",
|
|
||||||
// InsecureSkipVerify: true,
|
_, err = connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
// })
|
Name: queueName,
|
||||||
// Expect(connectionSettings).NotTo(BeNil())
|
})
|
||||||
// Expect(connectionSettings).To(BeAssignableToTypeOf(&ConnUrlHelper{}))
|
|
||||||
// err := amqpConnection.Open(context.Background(), connectionSettings)
|
Expect(err).To(BeNil())
|
||||||
// Expect(err).To(BeNil())
|
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: queueName},
|
||||||
//})
|
&PublisherOptions{
|
||||||
|
Id: "my_id",
|
||||||
|
SenderLinkName: "my_sender_link",
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
consumer, err := connection.NewConsumer(context.Background(), queueName, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(consumer).NotTo(BeNil())
|
||||||
|
// check the entity tracker
|
||||||
|
Expect(connection.entitiesTracker).NotTo(BeNil())
|
||||||
|
entLen := 0
|
||||||
|
connection.entitiesTracker.consumers.Range(func(key, value interface{}) bool {
|
||||||
|
entLen++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
Expect(entLen).To(Equal(1))
|
||||||
|
|
||||||
|
entLen = 0
|
||||||
|
connection.entitiesTracker.publishers.Range(func(key, value interface{}) bool {
|
||||||
|
entLen++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
Expect(entLen).To(Equal(1))
|
||||||
|
Expect(consumer.Close(context.Background())).To(BeNil())
|
||||||
|
Expect(publisher.Close(context.Background())).To(BeNil())
|
||||||
|
|
||||||
|
entLen = 0
|
||||||
|
connection.entitiesTracker.consumers.Range(func(key, value interface{}) bool {
|
||||||
|
entLen++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
Expect(entLen).To(Equal(0))
|
||||||
|
|
||||||
|
entLen = 0
|
||||||
|
connection.entitiesTracker.publishers.Range(func(key, value interface{}) bool {
|
||||||
|
entLen++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
Expect(entLen).To(Equal(0))
|
||||||
|
|
||||||
|
err = connection.Management().DeleteQueue(context.Background(), queueName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("AMQP TLS connection should succeed with in different vhosts with Anonymous and External.", func() {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(4)
|
||||||
|
DescribeTable("TLS connection should success in different vhosts ", func(virtualHost string, sasl amqp.SASLType) {
|
||||||
|
// Load CA cert
|
||||||
|
caCert, err := os.ReadFile("../../.ci/certs/ca_certificate.pem")
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// Create a CA certificate pool and add the CA certificate to it
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
caCertPool.AppendCertsFromPEM(caCert)
|
||||||
|
|
||||||
|
// Load client cert
|
||||||
|
clientCert, err := tls.LoadX509KeyPair("../../.ci/certs/client_localhost_certificate.pem",
|
||||||
|
"../../.ci/certs/client_localhost_key.pem")
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// Create a TLS configuration
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{clientCert},
|
||||||
|
RootCAs: caCertPool,
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
ServerName: "localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial the AMQP server with TLS configuration
|
||||||
|
connection, err := Dial(context.Background(), fmt.Sprintf("amqps://localhost:5671/%s", virtualHost), &AmqpConnOptions{
|
||||||
|
SASLType: sasl,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
|
||||||
|
// Close the connection
|
||||||
|
err = connection.Close(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
wg.Done()
|
||||||
|
},
|
||||||
|
Entry("with virtual host. External", "%2F", amqp.SASLTypeExternal("")),
|
||||||
|
Entry("with a not default virtual host. External", "tls", amqp.SASLTypeExternal("")),
|
||||||
|
Entry("with virtual host. Anonymous", "%2F", amqp.SASLTypeAnonymous()),
|
||||||
|
Entry("with a not default virtual host. Anonymous", "tls", amqp.SASLTypeAnonymous()),
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,23 @@ func (dc *DeliveryContext) Discard(ctx context.Context, e *amqp.Error) error {
|
||||||
return dc.receiver.RejectMessage(ctx, dc.message, e)
|
return dc.receiver.RejectMessage(ctx, dc.message, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *DeliveryContext) DiscardWithAnnotations(ctx context.Context, annotations amqp.Annotations) error {
|
// copyAnnotations helper function to copy annotations
|
||||||
|
func copyAnnotations(annotations amqp.Annotations) (amqp.Annotations, error) {
|
||||||
if err := validateMessageAnnotations(annotations); err != nil {
|
if err := validateMessageAnnotations(annotations); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
destination := make(amqp.Annotations)
|
||||||
|
for key, value := range annotations {
|
||||||
|
destination[key] = value
|
||||||
|
}
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DeliveryContext) DiscardWithAnnotations(ctx context.Context, annotations amqp.Annotations) error {
|
||||||
|
destination, err := copyAnnotations(annotations)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// copy the rabbitmq annotations to amqp annotations
|
|
||||||
destination := make(amqp.Annotations)
|
|
||||||
for keyA, value := range annotations {
|
|
||||||
destination[keyA] = value
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return dc.receiver.ModifyMessage(ctx, dc.message, &amqp.ModifyMessageOptions{
|
return dc.receiver.ModifyMessage(ctx, dc.message, &amqp.ModifyMessageOptions{
|
||||||
DeliveryFailed: true,
|
DeliveryFailed: true,
|
||||||
UndeliverableHere: true,
|
UndeliverableHere: true,
|
||||||
|
|
@ -48,15 +54,10 @@ func (dc *DeliveryContext) Requeue(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *DeliveryContext) RequeueWithAnnotations(ctx context.Context, annotations amqp.Annotations) error {
|
func (dc *DeliveryContext) RequeueWithAnnotations(ctx context.Context, annotations amqp.Annotations) error {
|
||||||
if err := validateMessageAnnotations(annotations); err != nil {
|
destination, err := copyAnnotations(annotations)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// copy the rabbitmq annotations to amqp annotations
|
|
||||||
destination := make(amqp.Annotations)
|
|
||||||
for key, value := range annotations {
|
|
||||||
destination[key] = value
|
|
||||||
|
|
||||||
}
|
|
||||||
return dc.receiver.ModifyMessage(ctx, dc.message, &amqp.ModifyMessageOptions{
|
return dc.receiver.ModifyMessage(ctx, dc.message, &amqp.ModifyMessageOptions{
|
||||||
DeliveryFailed: false,
|
DeliveryFailed: false,
|
||||||
UndeliverableHere: false,
|
UndeliverableHere: false,
|
||||||
|
|
@ -67,7 +68,7 @@ func (dc *DeliveryContext) RequeueWithAnnotations(ctx context.Context, annotatio
|
||||||
type Consumer struct {
|
type Consumer struct {
|
||||||
receiver atomic.Pointer[amqp.Receiver]
|
receiver atomic.Pointer[amqp.Receiver]
|
||||||
connection *AmqpConnection
|
connection *AmqpConnection
|
||||||
options ConsumerOptions
|
options IConsumerOptions
|
||||||
destinationAdd string
|
destinationAdd string
|
||||||
id string
|
id string
|
||||||
|
|
||||||
|
|
@ -84,10 +85,10 @@ func (c *Consumer) Id() string {
|
||||||
return c.id
|
return c.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConsumer(ctx context.Context, connection *AmqpConnection, destinationAdd string, options ConsumerOptions, args ...string) (*Consumer, error) {
|
func newConsumer(ctx context.Context, connection *AmqpConnection, destinationAdd string, options IConsumerOptions) (*Consumer, error) {
|
||||||
id := fmt.Sprintf("consumer-%s", uuid.New().String())
|
id := fmt.Sprintf("consumer-%s", uuid.New().String())
|
||||||
if len(args) > 0 {
|
if options != nil && options.id() != "" {
|
||||||
id = args[0]
|
id = options.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &Consumer{connection: connection, options: options,
|
r := &Consumer{connection: connection, options: options,
|
||||||
|
|
@ -144,5 +145,6 @@ func (c *Consumer) Receive(ctx context.Context) (*DeliveryContext, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Consumer) Close(ctx context.Context) error {
|
func (c *Consumer) Close(ctx context.Context) error {
|
||||||
|
c.connection.entitiesTracker.removeConsumer(c)
|
||||||
return c.receiver.Load().Close(ctx)
|
return c.receiver.Load().Close(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,31 +7,10 @@ import (
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
testhelper "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/test-helper"
|
testhelper "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/test-helper"
|
||||||
"strconv"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func publishMessagesWithStreamTag(queueName string, filterValue string, count int) {
|
|
||||||
conn, err := Dial(context.TODO(), []string{"amqp://guest:guest@localhost"}, nil)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
|
|
||||||
publisher, err := conn.NewPublisher(context.TODO(), &QueueAddress{Queue: queueName}, "producer_filter_stream")
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(publisher).NotTo(BeNil())
|
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
body := filterValue + " #" + strconv.Itoa(i)
|
|
||||||
msg := NewMessageWithFilter([]byte(body), filterValue)
|
|
||||||
publishResult, err := publisher.Publish(context.TODO(), msg)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(publishResult).NotTo(BeNil())
|
|
||||||
Expect(publishResult.Outcome).To(Equal(&amqp.StateAccepted{}))
|
|
||||||
}
|
|
||||||
err = conn.Close(context.TODO())
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("Consumer stream test", func() {
|
var _ = Describe("Consumer stream test", func() {
|
||||||
|
|
||||||
It("start consuming with different offset types", func() {
|
It("start consuming with different offset types", func() {
|
||||||
|
|
@ -49,7 +28,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
qName := generateName("start consuming with different offset types")
|
qName := generateName("start consuming with different offset types")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &StreamQueueSpecification{
|
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &StreamQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
|
|
@ -73,7 +52,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err := consumerOffsetValue.Receive(context.Background())
|
dc, err := consumerOffsetValue.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i+5)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i+5)))
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +67,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err := consumerFirst.Receive(context.Background())
|
dc, err := consumerFirst.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i)))
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +86,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err := consumerLast.Receive(context.Background())
|
dc, err := consumerLast.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).NotTo(Equal(fmt.Sprintf("Message #%d", 0)))
|
Expect(string(dc.Message().GetData())).NotTo(Equal(fmt.Sprintf("Message #%d", 0)))
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
|
|
||||||
consumerNext, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
consumerNext, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
||||||
|
|
@ -125,7 +104,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err = consumerNext.Receive(context.Background())
|
dc, err = consumerNext.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal("the next message"))
|
Expect(string(dc.Message().GetData())).To(Equal("the next message"))
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
signal <- struct{}{}
|
signal <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
@ -146,7 +125,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
qName := generateName("consumer should restart form the last offset in case of disconnection")
|
qName := generateName("consumer should restart form the last offset in case of disconnection")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
connection, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
ContainerID: qName,
|
ContainerID: qName,
|
||||||
RecoveryConfiguration: &RecoveryConfiguration{
|
RecoveryConfiguration: &RecoveryConfiguration{
|
||||||
|
|
@ -179,7 +158,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err := consumer.Receive(context.Background())
|
dc, err := consumer.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i)))
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,7 +179,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err := consumer.Receive(context.Background())
|
dc, err := consumer.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message #%d", i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect(consumer.Close(context.Background())).To(BeNil())
|
Expect(consumer.Close(context.Background())).To(BeNil())
|
||||||
|
|
@ -209,7 +188,7 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
})
|
})
|
||||||
It("consumer should filter messages based on x-stream-filter", func() {
|
It("consumer should filter messages based on x-stream-filter", func() {
|
||||||
qName := generateName("consumer should filter messages based on x-stream-filter")
|
qName := generateName("consumer should filter messages based on x-stream-filter")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &StreamQueueSpecification{
|
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &StreamQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
|
|
@ -217,15 +196,34 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfo).NotTo(BeNil())
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
Expect(queueInfo.name).To(Equal(qName))
|
Expect(queueInfo.name).To(Equal(qName))
|
||||||
publishMessagesWithStreamTag(qName, "banana", 10)
|
publishMessagesWithMessageLogic(qName, "banana", 10, func(msg *amqp.Message) {
|
||||||
publishMessagesWithStreamTag(qName, "apple", 10)
|
msg.Annotations = amqp.Annotations{
|
||||||
publishMessagesWithStreamTag(qName, "", 10)
|
// here we set the filter value taken from the filters array
|
||||||
|
StreamFilterValue: "banana",
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
publishMessagesWithMessageLogic(qName, "apple", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Annotations = amqp.Annotations{
|
||||||
|
// here we set the filter value taken from the filters array
|
||||||
|
StreamFilterValue: "apple",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Annotations = amqp.Annotations{
|
||||||
|
// here we set the filter value taken from the filters array
|
||||||
|
StreamFilterValue: "",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
consumerBanana, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
consumerBanana, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
||||||
ReceiverLinkName: "consumer banana should filter messages based on x-stream-filter",
|
ReceiverLinkName: "consumer banana should filter messages based on x-stream-filter",
|
||||||
InitialCredits: 200,
|
InitialCredits: 200,
|
||||||
Offset: &OffsetFirst{},
|
Offset: &OffsetFirst{},
|
||||||
Filters: []string{"banana"},
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
|
Values: []string{"banana"},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -235,16 +233,18 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err := consumerBanana.Receive(context.Background())
|
dc, err := consumerBanana.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("banana #%d", i)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i, "banana")))
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
consumerApple, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
consumerApple, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
||||||
ReceiverLinkName: "consumer apple should filter messages based on x-stream-filter",
|
ReceiverLinkName: "consumer apple should filter messages based on x-stream-filter",
|
||||||
InitialCredits: 200,
|
InitialCredits: 200,
|
||||||
Offset: &OffsetFirst{},
|
Offset: &OffsetFirst{},
|
||||||
Filters: []string{"apple"},
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
FilterMatchUnfiltered: true,
|
Values: []string{"apple"},
|
||||||
|
MatchUnfiltered: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -254,7 +254,8 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
dc, err := consumerApple.Receive(context.Background())
|
dc, err := consumerApple.Receive(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("apple #%d", i)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i, "apple")))
|
||||||
|
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +263,9 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
ReceiverLinkName: "consumer apple and banana should filter messages based on x-stream-filter",
|
ReceiverLinkName: "consumer apple and banana should filter messages based on x-stream-filter",
|
||||||
InitialCredits: 200,
|
InitialCredits: 200,
|
||||||
Offset: &OffsetFirst{},
|
Offset: &OffsetFirst{},
|
||||||
Filters: []string{"apple", "banana"},
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
|
Values: []string{"apple", "banana"},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -273,19 +276,23 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
if i < 10 {
|
if i < 10 {
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("banana #%d", i)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i, "banana")))
|
||||||
} else {
|
} else {
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("apple #%d", i-10)))
|
|
||||||
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i-10, "apple")))
|
||||||
|
|
||||||
}
|
}
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
consumerAppleMatchUnfiltered, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
consumerAppleMatchUnfiltered, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
||||||
ReceiverLinkName: "consumer apple should filter messages based on x-stream-filter and FilterMatchUnfiltered true",
|
ReceiverLinkName: "consumer apple should filter messages based on x-stream-filter and MatchUnfiltered true",
|
||||||
InitialCredits: 200,
|
InitialCredits: 200,
|
||||||
Offset: &OffsetFirst{},
|
Offset: &OffsetFirst{},
|
||||||
Filters: []string{"apple"},
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
FilterMatchUnfiltered: true,
|
Values: []string{"apple"},
|
||||||
|
MatchUnfiltered: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -296,10 +303,9 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(dc.Message()).NotTo(BeNil())
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
if i < 10 {
|
if i < 10 {
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf("apple #%d", i)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i, "apple")))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Expect(fmt.Sprintf("%s", dc.Message().GetData())).To(Equal(fmt.Sprintf(" #%d", i-10)))
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i-10, "")))
|
||||||
}
|
}
|
||||||
Expect(dc.Accept(context.Background())).To(BeNil())
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
}
|
}
|
||||||
|
|
@ -312,4 +318,263 @@ var _ = Describe("Consumer stream test", func() {
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("consumer should filter messages based on application properties", func() {
|
||||||
|
qName := generateName("consumer should filter messages based on application properties")
|
||||||
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &StreamQueueSpecification{
|
||||||
|
Name: qName,
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "ignoredKey", 7, func(msg *amqp.Message) {
|
||||||
|
msg.ApplicationProperties = map[string]interface{}{"ignoredKey": "ignoredValue"}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "key1", 10, func(msg *amqp.Message) {
|
||||||
|
msg.ApplicationProperties = map[string]interface{}{"key1": "value1", "constFilterKey": "constFilterValue"}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "key2", 10, func(msg *amqp.Message) {
|
||||||
|
msg.ApplicationProperties = map[string]interface{}{"key2": "value2", "constFilterKey": "constFilterValue"}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "key3", 10, func(msg *amqp.Message) {
|
||||||
|
msg.ApplicationProperties = map[string]interface{}{"key3": "value3", "constFilterKey": "constFilterValue"}
|
||||||
|
})
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(3)
|
||||||
|
DescribeTable("consumer should filter messages based on application properties", func(key string, value any, label string) {
|
||||||
|
|
||||||
|
consumer, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
||||||
|
InitialCredits: 200,
|
||||||
|
Offset: &OffsetFirst{},
|
||||||
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
|
ApplicationProperties: map[string]any{
|
||||||
|
key: value,
|
||||||
|
// this is a constant filter append during the publishMessagesWithApplicationProperties
|
||||||
|
// to test the multiple filters
|
||||||
|
"constFilterKey": "constFilterValue",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(consumer).NotTo(BeNil())
|
||||||
|
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
dc, err := consumer.Receive(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i, label)))
|
||||||
|
|
||||||
|
Expect(dc.message.ApplicationProperties).To(HaveKeyWithValue(key, value))
|
||||||
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
|
}
|
||||||
|
Expect(consumer.Close(context.Background())).To(BeNil())
|
||||||
|
wg.Done()
|
||||||
|
},
|
||||||
|
Entry("key1 value1", "key1", "value1", "key1"),
|
||||||
|
Entry("key2 value2", "key2", "value2", "key2"),
|
||||||
|
Entry("key3 value3", "key3", "value3", "key3"),
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
}()
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("consumer should filter messages based on properties", func() {
|
||||||
|
/*
|
||||||
|
Test the consumer should filter messages based on properties
|
||||||
|
*/
|
||||||
|
qName := generateName("consumer should filter messages based on properties")
|
||||||
|
qName += time.Now().String()
|
||||||
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &StreamQueueSpecification{
|
||||||
|
Name: qName,
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "MessageID", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{MessageID: "MessageID"}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "Subject", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{Subject: stringPtr("Subject")}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "ReplyTo", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{ReplyTo: stringPtr("ReplyTo")}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "ContentType", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{ContentType: stringPtr("ContentType")}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "ContentEncoding", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{ContentEncoding: stringPtr("ContentEncoding")}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "GroupID", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{GroupID: stringPtr("GroupID")}
|
||||||
|
})
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "ReplyToGroupID", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{ReplyToGroupID: stringPtr("ReplyToGroupID")}
|
||||||
|
})
|
||||||
|
|
||||||
|
// GroupSequence
|
||||||
|
publishMessagesWithMessageLogic(qName, "GroupSequence", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{GroupSequence: uint32Ptr(137)}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ReplyToGroupID
|
||||||
|
publishMessagesWithMessageLogic(qName, "ReplyToGroupID", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{ReplyToGroupID: stringPtr("ReplyToGroupID")}
|
||||||
|
})
|
||||||
|
|
||||||
|
// CreationTime
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "CreationTime", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{CreationTime: timePtr(createDateTime())}
|
||||||
|
})
|
||||||
|
|
||||||
|
// AbsoluteExpiryTime
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "AbsoluteExpiryTime", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{AbsoluteExpiryTime: timePtr(createDateTime())}
|
||||||
|
})
|
||||||
|
|
||||||
|
// CorrelationID
|
||||||
|
|
||||||
|
publishMessagesWithMessageLogic(qName, "CorrelationID", 10, func(msg *amqp.Message) {
|
||||||
|
msg.Properties = &amqp.MessageProperties{CorrelationID: "CorrelationID"}
|
||||||
|
})
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(12)
|
||||||
|
DescribeTable("consumer should filter messages based on properties", func(properties *amqp.MessageProperties, label string) {
|
||||||
|
|
||||||
|
consumer, err := connection.NewConsumer(context.Background(), qName, &StreamConsumerOptions{
|
||||||
|
InitialCredits: 200,
|
||||||
|
Offset: &OffsetFirst{},
|
||||||
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
|
Properties: properties,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(consumer).NotTo(BeNil())
|
||||||
|
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
dc, err := consumer.Receive(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(dc.Message()).NotTo(BeNil())
|
||||||
|
Expect(string(dc.Message().GetData())).To(Equal(fmt.Sprintf("Message_id:%d_label:%s", i, label)))
|
||||||
|
// we test one by one because of the date time fields
|
||||||
|
// It is not possible to compare the whole structure due of the time
|
||||||
|
// It is not perfect but it is enough for the test
|
||||||
|
if dc.message.Properties.MessageID != nil {
|
||||||
|
Expect(dc.message.Properties.MessageID).To(Equal(properties.MessageID))
|
||||||
|
}
|
||||||
|
if dc.message.Properties.Subject != nil {
|
||||||
|
Expect(dc.message.Properties.Subject).To(Equal(properties.Subject))
|
||||||
|
}
|
||||||
|
if dc.message.Properties.ReplyTo != nil {
|
||||||
|
Expect(dc.message.Properties.ReplyTo).To(Equal(properties.ReplyTo))
|
||||||
|
}
|
||||||
|
if dc.message.Properties.ContentType != nil {
|
||||||
|
Expect(dc.message.Properties.ContentType).To(Equal(properties.ContentType))
|
||||||
|
}
|
||||||
|
if dc.message.Properties.ContentEncoding != nil {
|
||||||
|
Expect(dc.message.Properties.ContentEncoding).To(Equal(properties.ContentEncoding))
|
||||||
|
}
|
||||||
|
if dc.message.Properties.GroupID != nil {
|
||||||
|
Expect(dc.message.Properties.GroupID).To(Equal(properties.GroupID))
|
||||||
|
}
|
||||||
|
if dc.message.Properties.ReplyToGroupID != nil {
|
||||||
|
Expect(dc.message.Properties.ReplyToGroupID).To(Equal(properties.ReplyToGroupID))
|
||||||
|
}
|
||||||
|
if dc.message.Properties.GroupSequence != nil {
|
||||||
|
Expect(dc.message.Properties.GroupSequence).To(Equal(properties.GroupSequence))
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.message.Properties.ReplyToGroupID != nil {
|
||||||
|
Expect(dc.message.Properties.ReplyToGroupID).To(Equal(properties.ReplyToGroupID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we compare only the year, month and day
|
||||||
|
// it is not perfect but it is enough for the test
|
||||||
|
if dc.message.Properties.CreationTime != nil {
|
||||||
|
Expect(dc.message.Properties.CreationTime.Year()).To(Equal(properties.CreationTime.Year()))
|
||||||
|
Expect(dc.message.Properties.CreationTime.Month()).To(Equal(properties.CreationTime.Month()))
|
||||||
|
Expect(dc.message.Properties.CreationTime.Day()).To(Equal(properties.CreationTime.Day()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.message.Properties.AbsoluteExpiryTime != nil {
|
||||||
|
Expect(dc.message.Properties.AbsoluteExpiryTime.Year()).To(Equal(properties.AbsoluteExpiryTime.Year()))
|
||||||
|
Expect(dc.message.Properties.AbsoluteExpiryTime.Month()).To(Equal(properties.AbsoluteExpiryTime.Month()))
|
||||||
|
Expect(dc.message.Properties.AbsoluteExpiryTime.Day()).To(Equal(properties.AbsoluteExpiryTime.Day()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.message.Properties.CorrelationID != nil {
|
||||||
|
Expect(dc.message.Properties.CorrelationID).To(Equal(properties.CorrelationID))
|
||||||
|
}
|
||||||
|
|
||||||
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
|
}
|
||||||
|
Expect(consumer.Close(context.Background())).To(BeNil())
|
||||||
|
wg.Done()
|
||||||
|
},
|
||||||
|
Entry("MessageID", &amqp.MessageProperties{MessageID: "MessageID"}, "MessageID"),
|
||||||
|
Entry("Subject", &amqp.MessageProperties{Subject: stringPtr("Subject")}, "Subject"),
|
||||||
|
Entry("ReplyTo", &amqp.MessageProperties{ReplyTo: stringPtr("ReplyTo")}, "ReplyTo"),
|
||||||
|
Entry("ContentType", &amqp.MessageProperties{ContentType: stringPtr("ContentType")}, "ContentType"),
|
||||||
|
Entry("ContentEncoding", &amqp.MessageProperties{ContentEncoding: stringPtr("ContentEncoding")}, "ContentEncoding"),
|
||||||
|
Entry("GroupID", &amqp.MessageProperties{GroupID: stringPtr("GroupID")}, "GroupID"),
|
||||||
|
Entry("ReplyToGroupID", &amqp.MessageProperties{ReplyToGroupID: stringPtr("ReplyToGroupID")}, "ReplyToGroupID"),
|
||||||
|
Entry("GroupSequence", &amqp.MessageProperties{GroupSequence: uint32Ptr(137)}, "GroupSequence"),
|
||||||
|
Entry("ReplyToGroupID", &amqp.MessageProperties{ReplyToGroupID: stringPtr("ReplyToGroupID")}, "ReplyToGroupID"),
|
||||||
|
Entry("CreationTime", &amqp.MessageProperties{CreationTime: timePtr(createDateTime())}, "CreationTime"),
|
||||||
|
Entry("AbsoluteExpiryTime", &amqp.MessageProperties{AbsoluteExpiryTime: timePtr(createDateTime())}, "AbsoluteExpiryTime"),
|
||||||
|
Entry("CorrelationID", &amqp.MessageProperties{CorrelationID: "CorrelationID"}, "CorrelationID"),
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type msgLogic = func(*amqp.Message)
|
||||||
|
|
||||||
|
func publishMessagesWithMessageLogic(queue string, label string, count int, logic msgLogic) {
|
||||||
|
conn, err := Dial(context.TODO(), "amqp://guest:guest@localhost", nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
publisher, err := conn.NewPublisher(context.TODO(), &QueueAddress{Queue: queue},
|
||||||
|
nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
body := fmt.Sprintf("Message_id:%d_label:%s", i, label)
|
||||||
|
msg := NewMessage([]byte(body))
|
||||||
|
logic(msg)
|
||||||
|
publishResult, err := publisher.Publish(context.TODO(), msg)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publishResult).NotTo(BeNil())
|
||||||
|
Expect(publishResult.Outcome).To(Equal(&amqp.StateAccepted{}))
|
||||||
|
}
|
||||||
|
err = conn.Close(context.TODO())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
|
|
||||||
It("AMQP NewConsumer should fail due to context cancellation", func() {
|
It("AMQP NewConsumer should fail due to context cancellation", func() {
|
||||||
qName := generateNameWithDateTime("AMQP NewConsumer should fail due to context cancellation")
|
qName := generateNameWithDateTime("AMQP NewConsumer should fail due to context cancellation")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
|
|
@ -33,7 +33,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
|
|
||||||
It("AMQP NewConsumer should ack and empty the queue", func() {
|
It("AMQP NewConsumer should ack and empty the queue", func() {
|
||||||
qName := generateNameWithDateTime("AMQP NewConsumer should ack and empty the queue")
|
qName := generateNameWithDateTime("AMQP NewConsumer should ack and empty the queue")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
|
|
@ -62,7 +62,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
It("AMQP NewConsumer should requeue the message to the queue", func() {
|
It("AMQP NewConsumer should requeue the message to the queue", func() {
|
||||||
|
|
||||||
qName := generateNameWithDateTime("AMQP NewConsumer should requeue the message to the queue")
|
qName := generateNameWithDateTime("AMQP NewConsumer should requeue the message to the queue")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
|
|
@ -81,6 +81,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
Expect(consumer.Close(context.Background())).To(BeNil())
|
Expect(consumer.Close(context.Background())).To(BeNil())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
Expect(nMessages).To(Equal(1))
|
Expect(nMessages).To(Equal(1))
|
||||||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
|
@ -89,7 +90,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
It("AMQP NewConsumer should requeue the message to the queue with annotations", func() {
|
It("AMQP NewConsumer should requeue the message to the queue with annotations", func() {
|
||||||
|
|
||||||
qName := generateNameWithDateTime("AMQP NewConsumer should requeue the message to the queue with annotations")
|
qName := generateNameWithDateTime("AMQP NewConsumer should requeue the message to the queue with annotations")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
|
|
@ -116,6 +117,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
Expect(consumer.Close(context.Background())).To(BeNil())
|
Expect(consumer.Close(context.Background())).To(BeNil())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
Expect(nMessages).To(Equal(1))
|
Expect(nMessages).To(Equal(1))
|
||||||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
|
@ -124,7 +126,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
It("AMQP NewConsumer should discard the message to the queue with and without annotations", func() {
|
It("AMQP NewConsumer should discard the message to the queue with and without annotations", func() {
|
||||||
// TODO: Implement this test with a dead letter queue to test the discard feature
|
// TODO: Implement this test with a dead letter queue to test the discard feature
|
||||||
qName := generateNameWithDateTime("AMQP NewConsumer should discard the message to the queue with and without annotations")
|
qName := generateNameWithDateTime("AMQP NewConsumer should discard the message to the queue with and without annotations")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
|
|
@ -153,6 +155,7 @@ var _ = Describe("NewConsumer tests", func() {
|
||||||
Info: nil,
|
Info: nil,
|
||||||
})).To(BeNil())
|
})).To(BeNil())
|
||||||
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
Expect(nMessages).To(Equal(0))
|
Expect(nMessages).To(Equal(0))
|
||||||
Expect(consumer.Close(context.Background())).To(BeNil())
|
Expect(consumer.Close(context.Background())).To(BeNil())
|
||||||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
|
|
|
||||||
|
|
@ -3,47 +3,103 @@ package rabbitmqamqp
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Environment struct {
|
type TEndPointStrategy int
|
||||||
connections sync.Map
|
|
||||||
addresses []string
|
const (
|
||||||
connOptions *AmqpConnOptions
|
StrategyRandom TEndPointStrategy = iota
|
||||||
|
StrategySequential TEndPointStrategy = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
Address string
|
||||||
|
Options *AmqpConnOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEnvironment(addresses []string, connOptions *AmqpConnOptions) *Environment {
|
func DefaultEndpoints() []Endpoint {
|
||||||
|
ep := Endpoint{
|
||||||
|
Address: "amqp://",
|
||||||
|
Options: &AmqpConnOptions{
|
||||||
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Endpoint{ep}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Environment struct {
|
||||||
|
connections sync.Map
|
||||||
|
endPoints []Endpoint
|
||||||
|
EndPointStrategy TEndPointStrategy
|
||||||
|
nextConnectionId int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnvironment(address string, options *AmqpConnOptions) *Environment {
|
||||||
|
return NewClusterEnvironmentWithStrategy([]Endpoint{{Address: address, Options: options}}, StrategyRandom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClusterEnvironment(endPoints []Endpoint) *Environment {
|
||||||
|
return NewClusterEnvironmentWithStrategy(endPoints, StrategyRandom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClusterEnvironmentWithStrategy(endPoints []Endpoint, strategy TEndPointStrategy) *Environment {
|
||||||
return &Environment{
|
return &Environment{
|
||||||
connections: sync.Map{},
|
connections: sync.Map{},
|
||||||
addresses: addresses,
|
endPoints: endPoints,
|
||||||
connOptions: connOptions,
|
EndPointStrategy: strategy,
|
||||||
|
nextConnectionId: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConnection get a new connection from the environment.
|
// NewConnection get a new connection from the environment.
|
||||||
// If the connection id is provided, it will be used as the connection id.
|
// It picks an endpoint from the list of endpoints, based on EndPointStrategy, and tries to open a connection.
|
||||||
// If the connection id is not provided, a new connection id will be generated.
|
// It fails if all the endpoints are not reachable.
|
||||||
// The connection id is unique in the environment.
|
|
||||||
// The Environment will keep track of the connection and close it when the environment is closed.
|
// The Environment will keep track of the connection and close it when the environment is closed.
|
||||||
func (e *Environment) NewConnection(ctx context.Context, args ...string) (*AmqpConnection, error) {
|
func (e *Environment) NewConnection(ctx context.Context) (*AmqpConnection, error) {
|
||||||
if len(args) > 0 && len(args[0]) > 0 {
|
|
||||||
// check if connection already exists
|
|
||||||
if _, ok := e.connections.Load(args[0]); ok {
|
|
||||||
return nil, fmt.Errorf("connection with id %s already exists", args[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection, err := Dial(ctx, e.addresses, e.connOptions, args...)
|
tmp := make([]Endpoint, len(e.endPoints))
|
||||||
if err != nil {
|
copy(tmp, e.endPoints)
|
||||||
return nil, err
|
lastError := error(nil)
|
||||||
|
for len(tmp) > 0 {
|
||||||
|
idx := 0
|
||||||
|
|
||||||
|
switch e.EndPointStrategy {
|
||||||
|
case StrategyRandom:
|
||||||
|
idx = random(len(tmp))
|
||||||
|
case StrategySequential:
|
||||||
|
idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := tmp[idx]
|
||||||
|
// remove the index from the tmp list
|
||||||
|
tmp = append(tmp[:idx], tmp[idx+1:]...)
|
||||||
|
var cloned *AmqpConnOptions
|
||||||
|
if addr.Options != nil {
|
||||||
|
cloned = addr.Options.Clone()
|
||||||
|
}
|
||||||
|
connection, err := Dial(ctx, addr.Address, cloned)
|
||||||
|
if err != nil {
|
||||||
|
Error("Failed to open connection", ExtractWithoutPassword(addr.Address), err)
|
||||||
|
lastError = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we use it to make each connection unique
|
||||||
|
atomic.AddInt32(&e.nextConnectionId, 1)
|
||||||
|
connection.amqpConnOptions.Id = fmt.Sprintf("%s_%d", connection.amqpConnOptions.Id, e.nextConnectionId)
|
||||||
|
e.connections.Store(connection.Id(), connection)
|
||||||
|
|
||||||
|
connection.refMap = &e.connections
|
||||||
|
return connection, nil
|
||||||
}
|
}
|
||||||
e.connections.Store(connection.Id(), connection)
|
return nil, fmt.Errorf("fail to open connection. Last error: %w", lastError)
|
||||||
connection.refMap = &e.connections
|
|
||||||
return connection, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connections gets the active connections in the environment
|
// Connections gets the active connections in the environment
|
||||||
|
|
||||||
func (e *Environment) Connections() []*AmqpConnection {
|
func (e *Environment) Connections() []*AmqpConnection {
|
||||||
connections := make([]*AmqpConnection, 0)
|
connections := make([]*AmqpConnection, 0)
|
||||||
e.connections.Range(func(key, value interface{}) bool {
|
e.connections.Range(func(key, value interface{}) bool {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ package rabbitmqamqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("AMQP Environment Test", func() {
|
var _ = Describe("AMQP Environment Test", func() {
|
||||||
It("AMQP Environment connection should succeed", func() {
|
It("AMQP Environment connection should succeed", func() {
|
||||||
env := NewEnvironment([]string{"amqp://"}, nil)
|
env := NewClusterEnvironment([]Endpoint{{Address: "amqp://"}})
|
||||||
Expect(env).NotTo(BeNil())
|
Expect(env).NotTo(BeNil())
|
||||||
Expect(env.Connections()).NotTo(BeNil())
|
Expect(env.Connections()).NotTo(BeNil())
|
||||||
Expect(len(env.Connections())).To(Equal(0))
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
|
|
@ -22,7 +23,7 @@ var _ = Describe("AMQP Environment Test", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP Environment CloseConnections should remove all the elements form the list", func() {
|
It("AMQP Environment CloseConnections should remove all the elements form the list", func() {
|
||||||
env := NewEnvironment([]string{"amqp://"}, nil)
|
env := NewClusterEnvironment([]Endpoint{{Address: "amqp://"}})
|
||||||
Expect(env).NotTo(BeNil())
|
Expect(env).NotTo(BeNil())
|
||||||
Expect(env.Connections()).NotTo(BeNil())
|
Expect(env.Connections()).NotTo(BeNil())
|
||||||
Expect(len(env.Connections())).To(Equal(0))
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
|
|
@ -36,22 +37,83 @@ var _ = Describe("AMQP Environment Test", func() {
|
||||||
Expect(len(env.Connections())).To(Equal(0))
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP Environment connection ID should be unique", func() {
|
It("Get new connection should connect to the one correct uri and fails the others", func() {
|
||||||
env := NewEnvironment([]string{"amqp://"}, nil)
|
|
||||||
|
env := NewClusterEnvironment([]Endpoint{{Address: "amqp://localhost:1234"}, {Address: "amqp://nohost:555"}, {Address: "amqp://"}})
|
||||||
|
conn, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn.Close(context.Background()))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Get new connection should fail due of wrong Port", func() {
|
||||||
|
env := NewClusterEnvironment([]Endpoint{{Address: "amqp://localhost:1234"}})
|
||||||
|
_, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("AMQP connection should fail due of wrong Host", func() {
|
||||||
|
env := NewClusterEnvironment([]Endpoint{{Address: "amqp://wrong_host:5672"}})
|
||||||
|
_, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("AMQP connection should fails with all the wrong uris", func() {
|
||||||
|
env := NewClusterEnvironment([]Endpoint{{Address: "amqp://localhost:1234"}, {Address: "amqp://nohost:555"}, {Address: "amqp://nono"}})
|
||||||
|
_, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("AMQP connection should success in different vhosts", func() {
|
||||||
|
// user_1 and vhost_user_1 are preloaded in the rabbitmq server during the startup
|
||||||
|
env := NewEnvironment("amqp://user_1:user_1@localhost:5672/vhost_user_1", nil)
|
||||||
Expect(env).NotTo(BeNil())
|
Expect(env).NotTo(BeNil())
|
||||||
Expect(env.Connections()).NotTo(BeNil())
|
Expect(env.Connections()).NotTo(BeNil())
|
||||||
Expect(len(env.Connections())).To(Equal(0))
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
connection, err := env.NewConnection(context.Background(), "myConnectionId")
|
conn, err := env.NewConnection(context.Background())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(conn.Close(context.Background()))
|
||||||
Expect(len(env.Connections())).To(Equal(1))
|
|
||||||
connectionShouldBeNil, err := env.NewConnection(context.Background(), "myConnectionId")
|
|
||||||
Expect(err).NotTo(BeNil())
|
|
||||||
Expect(err.Error()).To(ContainSubstring("connection with id myConnectionId already exists"))
|
|
||||||
Expect(connectionShouldBeNil).To(BeNil())
|
|
||||||
Expect(len(env.Connections())).To(Equal(1))
|
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
|
||||||
Expect(len(env.Connections())).To(Equal(0))
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("AMQP connection should fail with user_1 does not have the grant for /", func() {
|
||||||
|
// user_1 is preloaded in the rabbitmq server during the startup
|
||||||
|
env := NewEnvironment("amqp://user_1:user_1@localhost:5672/", nil)
|
||||||
|
Expect(env).NotTo(BeNil())
|
||||||
|
_, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Environment strategy", func() {
|
||||||
|
DescribeTable("Environment with strategy should success", func(strategy TEndPointStrategy) {
|
||||||
|
env := NewClusterEnvironmentWithStrategy([]Endpoint{{Address: "amqp://", Options: &AmqpConnOptions{Id: "my"}}, {Address: "amqp://nohost:555"}, {Address: "amqp://nono"}}, StrategyRandom)
|
||||||
|
Expect(env).NotTo(BeNil())
|
||||||
|
Expect(env.Connections()).NotTo(BeNil())
|
||||||
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
|
conn, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn.Id()).To(Equal("my_1"))
|
||||||
|
Expect(conn.Close(context.Background()))
|
||||||
|
},
|
||||||
|
Entry("StrategyRandom", StrategyRandom),
|
||||||
|
Entry("StrategySequential", StrategySequential),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Environment should success even partial options", func() {
|
||||||
|
DescribeTable("Environment should success even partial options", func(options *AmqpConnOptions) {
|
||||||
|
|
||||||
|
env := NewClusterEnvironment([]Endpoint{{Address: "amqp://", Options: options}})
|
||||||
|
Expect(env).NotTo(BeNil())
|
||||||
|
Expect(env.Connections()).NotTo(BeNil())
|
||||||
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
|
conn, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn.Close(context.Background()))
|
||||||
|
|
||||||
|
},
|
||||||
|
Entry("Partial options", &AmqpConnOptions{Id: "my"}),
|
||||||
|
Entry("Partial options", &AmqpConnOptions{SASLType: amqp.SASLTypeAnonymous()}),
|
||||||
|
Entry("Partial options", &AmqpConnOptions{ContainerID: "cid_my"}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ var _ = Describe("AMQP Exchange test ", func() {
|
||||||
var connection *AmqpConnection
|
var connection *AmqpConnection
|
||||||
var management *AmqpManagement
|
var management *AmqpManagement
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
conn, err := Dial(context.TODO(), []string{"amqp://"}, nil)
|
conn, err := Dial(context.TODO(), "amqp://", nil)
|
||||||
connection = conn
|
connection = conn
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
management = connection.Management()
|
management = connection.Management()
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Azure/go-amqp"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrPreconditionFailed = errors.New("precondition Failed")
|
var ErrPreconditionFailed = errors.New("precondition Failed")
|
||||||
|
|
@ -22,10 +23,9 @@ type AmqpManagement struct {
|
||||||
sender *amqp.Sender
|
sender *amqp.Sender
|
||||||
receiver *amqp.Receiver
|
receiver *amqp.Receiver
|
||||||
lifeCycle *LifeCycle
|
lifeCycle *LifeCycle
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAmqpManagement() *AmqpManagement {
|
func newAmqpManagement() *AmqpManagement {
|
||||||
return &AmqpManagement{
|
return &AmqpManagement{
|
||||||
lifeCycle: NewLifeCycle(),
|
lifeCycle: NewLifeCycle(),
|
||||||
}
|
}
|
||||||
|
|
@ -173,9 +173,9 @@ func (a *AmqpManagement) request(ctx context.Context, id string, body any, path
|
||||||
return make(map[string]any), nil
|
return make(map[string]any), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification QueueSpecification) (*AmqpQueueInfo, error) {
|
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification IQueueSpecification) (*AmqpQueueInfo, error) {
|
||||||
if specification == nil {
|
if specification == nil {
|
||||||
return nil, fmt.Errorf("queue specification cannot be nil. You need to provide a valid QueueSpecification")
|
return nil, fmt.Errorf("queue specification cannot be nil. You need to provide a valid IQueueSpecification")
|
||||||
}
|
}
|
||||||
|
|
||||||
amqpQueue := newAmqpQueue(a, specification.name())
|
amqpQueue := newAmqpQueue(a, specification.name())
|
||||||
|
|
@ -192,9 +192,9 @@ func (a *AmqpManagement) DeleteQueue(ctx context.Context, name string) error {
|
||||||
return q.Delete(ctx)
|
return q.Delete(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) DeclareExchange(ctx context.Context, exchangeSpecification ExchangeSpecification) (*AmqpExchangeInfo, error) {
|
func (a *AmqpManagement) DeclareExchange(ctx context.Context, exchangeSpecification IExchangeSpecification) (*AmqpExchangeInfo, error) {
|
||||||
if exchangeSpecification == nil {
|
if exchangeSpecification == nil {
|
||||||
return nil, errors.New("exchange specification cannot be nil. You need to provide a valid ExchangeSpecification")
|
return nil, errors.New("exchange specification cannot be nil. You need to provide a valid IExchangeSpecification")
|
||||||
}
|
}
|
||||||
|
|
||||||
exchange := newAmqpExchange(a, exchangeSpecification.name())
|
exchange := newAmqpExchange(a, exchangeSpecification.name())
|
||||||
|
|
@ -208,9 +208,9 @@ func (a *AmqpManagement) DeleteExchange(ctx context.Context, name string) error
|
||||||
return e.Delete(ctx)
|
return e.Delete(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) Bind(ctx context.Context, bindingSpecification BindingSpecification) (string, error) {
|
func (a *AmqpManagement) Bind(ctx context.Context, bindingSpecification IBindingSpecification) (string, error) {
|
||||||
if bindingSpecification == nil {
|
if bindingSpecification == nil {
|
||||||
return "", fmt.Errorf("binding specification cannot be nil. You need to provide a valid BindingSpecification")
|
return "", fmt.Errorf("binding specification cannot be nil. You need to provide a valid IBindingSpecification")
|
||||||
}
|
}
|
||||||
|
|
||||||
bind := newAMQPBinding(a)
|
bind := newAMQPBinding(a)
|
||||||
|
|
@ -220,9 +220,9 @@ func (a *AmqpManagement) Bind(ctx context.Context, bindingSpecification BindingS
|
||||||
return bind.Bind(ctx)
|
return bind.Bind(ctx)
|
||||||
|
|
||||||
}
|
}
|
||||||
func (a *AmqpManagement) Unbind(ctx context.Context, bindingPath string) error {
|
func (a *AmqpManagement) Unbind(ctx context.Context, path string) error {
|
||||||
bind := newAMQPBinding(a)
|
bind := newAMQPBinding(a)
|
||||||
return bind.Unbind(ctx, bindingPath)
|
return bind.Unbind(ctx, path)
|
||||||
}
|
}
|
||||||
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*AmqpQueueInfo, error) {
|
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*AmqpQueueInfo, error) {
|
||||||
path, err := queueAddress(&queueName)
|
path, err := queueAddress(&queueName)
|
||||||
|
|
@ -236,15 +236,22 @@ func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*Amqp
|
||||||
return newAmqpQueueInfo(result), nil
|
return newAmqpQueueInfo(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) PurgeQueue(ctx context.Context, queueName string) (int, error) {
|
// PurgeQueue purges the queue
|
||||||
purge := newAmqpQueue(a, queueName)
|
// returns the number of messages purged
|
||||||
|
func (a *AmqpManagement) PurgeQueue(ctx context.Context, name string) (int, error) {
|
||||||
|
purge := newAmqpQueue(a, name)
|
||||||
return purge.Purge(ctx)
|
return purge.Purge(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AmqpManagement) refreshToken(ctx context.Context, token string) error {
|
||||||
|
_, err := a.Request(ctx, []byte(token), authTokens, commandPut, []int{responseCode204})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) NotifyStatusChange(channel chan *StateChanged) {
|
func (a *AmqpManagement) NotifyStatusChange(channel chan *StateChanged) {
|
||||||
a.lifeCycle.chStatusChanged = channel
|
a.lifeCycle.chStatusChanged = channel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) State() LifeCycleState {
|
func (a *AmqpManagement) State() ILifeCycleState {
|
||||||
return a.lifeCycle.State()
|
return a.lifeCycle.State()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
var _ = Describe("Management tests", func() {
|
var _ = Describe("Management tests", func() {
|
||||||
It("AMQP Management should fail due to context cancellation", func() {
|
It("AMQP Management should fail due to context cancellation", func() {
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||||
|
|
@ -23,7 +23,7 @@ var _ = Describe("Management tests", func() {
|
||||||
|
|
||||||
It("AMQP Management should receive events", func() {
|
It("AMQP Management should receive events", func() {
|
||||||
ch := make(chan *StateChanged, 2)
|
ch := make(chan *StateChanged, 2)
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, &AmqpConnOptions{
|
connection, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous(),
|
SASLType: amqp.SASLTypeAnonymous(),
|
||||||
RecoveryConfiguration: &RecoveryConfiguration{
|
RecoveryConfiguration: &RecoveryConfiguration{
|
||||||
ActiveRecovery: false,
|
ActiveRecovery: false,
|
||||||
|
|
@ -43,27 +43,31 @@ var _ = Describe("Management tests", func() {
|
||||||
|
|
||||||
It("Request", func() {
|
It("Request", func() {
|
||||||
|
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
management := connection.Management()
|
management := connection.Management()
|
||||||
kv := make(map[string]any)
|
kv := make(map[string]any)
|
||||||
kv["durable"] = true
|
kv["durable"] = true
|
||||||
kv["auto_delete"] = false
|
kv["auto_delete"] = true
|
||||||
_queueArguments := make(map[string]any)
|
_queueArguments := make(map[string]any)
|
||||||
_queueArguments["x-queue-type"] = "quorum"
|
_queueArguments["x-queue-type"] = "classic"
|
||||||
kv["arguments"] = _queueArguments
|
kv["arguments"] = _queueArguments
|
||||||
path := "/queues/test"
|
path := "/queues/test"
|
||||||
result, err := management.Request(context.Background(), kv, path, "PUT", []int{200})
|
result, err := management.Request(context.Background(), kv, path, "PUT", []int{200})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(result).NotTo(BeNil())
|
Expect(result).NotTo(BeNil())
|
||||||
|
result, err = management.Request(context.Background(), amqp.Null{}, path, "DELETE", []int{responseCode200})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(result).NotTo(BeNil())
|
||||||
Expect(management.Close(context.Background())).To(BeNil())
|
Expect(management.Close(context.Background())).To(BeNil())
|
||||||
|
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("GET on non-existing queue returns ErrDoesNotExist", func() {
|
It("GET on non-existing queue returns ErrDoesNotExist", func() {
|
||||||
|
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
management := connection.Management()
|
management := connection.Management()
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@ func (m *Publisher) Id() string {
|
||||||
return m.id
|
return m.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPublisher(ctx context.Context, connection *AmqpConnection, destinationAdd string, linkName string, args ...string) (*Publisher, error) {
|
func newPublisher(ctx context.Context, connection *AmqpConnection, destinationAdd string, options IPublisherOptions) (*Publisher, error) {
|
||||||
id := fmt.Sprintf("publisher-%s", uuid.New().String())
|
id := fmt.Sprintf("publisher-%s", uuid.New().String())
|
||||||
if len(args) > 0 {
|
if options != nil && options.id() != "" {
|
||||||
id = args[0]
|
id = options.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &Publisher{connection: connection, linkName: linkName, destinationAdd: destinationAdd, id: id}
|
r := &Publisher{connection: connection, linkName: getLinkName(options), destinationAdd: destinationAdd, id: id}
|
||||||
connection.entitiesTracker.storeOrReplaceProducer(r)
|
connection.entitiesTracker.storeOrReplaceProducer(r)
|
||||||
err := r.createSender(ctx)
|
err := r.createSender(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -62,7 +62,7 @@ RabbitMQ supports the following DeliveryState types:
|
||||||
- StateRejected
|
- StateRejected
|
||||||
See: https://www.rabbitmq.com/docs/next/amqp#outcomes for more information.
|
See: https://www.rabbitmq.com/docs/next/amqp#outcomes for more information.
|
||||||
|
|
||||||
Note: If the destination address is not defined during the creation, the message must have a TO property set.
|
If the destination address is not defined during the creation, the message must have a TO property set.
|
||||||
You can use the helper "MessagePropertyToAddress" to create the destination address.
|
You can use the helper "MessagePropertyToAddress" to create the destination address.
|
||||||
See the examples:
|
See the examples:
|
||||||
Create a new publisher that sends messages to a specific destination address:
|
Create a new publisher that sends messages to a specific destination address:
|
||||||
|
|
@ -84,6 +84,16 @@ Create a new publisher that sends messages based on message destination address:
|
||||||
..:= MessagePropertyToAddress(msg, &QueueAddress{Queue: "myQueueName"})
|
..:= MessagePropertyToAddress(msg, &QueueAddress{Queue: "myQueueName"})
|
||||||
..:= publisher.Publish(context.Background(), msg)
|
..:= publisher.Publish(context.Background(), msg)
|
||||||
|
|
||||||
|
</code>
|
||||||
|
|
||||||
|
The message is persistent by default by setting the Header.Durable to true when Header is nil.
|
||||||
|
You can set the message to be non-persistent by setting the Header.Durable to false.
|
||||||
|
Note:
|
||||||
|
When you use the `Header` is up to you to set the message properties,
|
||||||
|
You need set the `Header.Durable` to true or false.
|
||||||
|
|
||||||
|
<code>
|
||||||
|
|
||||||
</code>
|
</code>
|
||||||
*/
|
*/
|
||||||
func (m *Publisher) Publish(ctx context.Context, message *amqp.Message) (*PublishResult, error) {
|
func (m *Publisher) Publish(ctx context.Context, message *amqp.Message) (*PublishResult, error) {
|
||||||
|
|
@ -97,6 +107,14 @@ func (m *Publisher) Publish(ctx context.Context, message *amqp.Message) (*Publis
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the default persistence to the message
|
||||||
|
if message.Header == nil {
|
||||||
|
message.Header = &amqp.MessageHeader{
|
||||||
|
Durable: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r, err := m.sender.Load().SendWithReceipt(ctx, message, nil)
|
r, err := m.sender.Load().SendWithReceipt(ctx, message, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
var _ = Describe("AMQP publisher ", func() {
|
var _ = Describe("AMQP publisher ", func() {
|
||||||
It("Send a message to a queue with a Message Target NewPublisher", func() {
|
It("Send a message to a queue with a Message Target NewPublisher", func() {
|
||||||
qName := generateNameWithDateTime("Send a message to a queue with a Message Target NewPublisher")
|
qName := generateNameWithDateTime("Send a message to a queue with a Message Target NewPublisher")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
|
|
@ -18,7 +18,7 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfo).NotTo(BeNil())
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, "test")
|
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
Expect(publisher).To(BeAssignableToTypeOf(&Publisher{}))
|
Expect(publisher).To(BeAssignableToTypeOf(&Publisher{}))
|
||||||
|
|
@ -36,11 +36,11 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("NewPublisher should fail to a not existing exchange", func() {
|
It("NewPublisher should fail to a not existing exchange", func() {
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
exchangeName := "Nope"
|
exchangeName := "Nope"
|
||||||
publisher, err := connection.NewPublisher(context.Background(), &ExchangeAddress{Exchange: exchangeName}, "test")
|
publisher, err := connection.NewPublisher(context.Background(), &ExchangeAddress{Exchange: exchangeName}, nil)
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
Expect(publisher).To(BeNil())
|
Expect(publisher).To(BeNil())
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
|
@ -48,7 +48,7 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
|
|
||||||
It("publishResult should released to a not existing routing key", func() {
|
It("publishResult should released to a not existing routing key", func() {
|
||||||
eName := generateNameWithDateTime("publishResult should released to a not existing routing key")
|
eName := generateNameWithDateTime("publishResult should released to a not existing routing key")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
exchange, err := connection.Management().DeclareExchange(context.Background(), &TopicExchangeSpecification{
|
exchange, err := connection.Management().DeclareExchange(context.Background(), &TopicExchangeSpecification{
|
||||||
|
|
@ -62,7 +62,7 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
publisher, err := connection.NewPublisher(context.Background(), &ExchangeAddress{
|
publisher, err := connection.NewPublisher(context.Background(), &ExchangeAddress{
|
||||||
Exchange: eName,
|
Exchange: eName,
|
||||||
Key: routingKeyNope,
|
Key: routingKeyNope,
|
||||||
}, "test")
|
}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
publishResult, err := publisher.Publish(context.Background(), NewMessage([]byte("hello")))
|
publishResult, err := publisher.Publish(context.Background(), NewMessage([]byte("hello")))
|
||||||
|
|
@ -75,14 +75,14 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
|
|
||||||
It("Send a message to a deleted queue should fail", func() {
|
It("Send a message to a deleted queue should fail", func() {
|
||||||
qName := generateNameWithDateTime("Send a message to a deleted queue should fail")
|
qName := generateNameWithDateTime("Send a message to a deleted queue should fail")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
_, err = connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
_, err = connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, "test")
|
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
publishResult, err := publisher.Publish(context.Background(), NewMessage([]byte("hello")))
|
publishResult, err := publisher.Publish(context.Background(), NewMessage([]byte("hello")))
|
||||||
|
|
@ -91,15 +91,16 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
err = connection.management.DeleteQueue(context.Background(), qName)
|
err = connection.management.DeleteQueue(context.Background(), qName)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
publishResult, err = publisher.Publish(context.Background(), NewMessage([]byte("hello")))
|
publishResult, err = publisher.Publish(context.Background(), NewMessage([]byte("hello")))
|
||||||
|
Expect(publishResult).To(BeNil())
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
Expect(connection.Close(context.Background()))
|
Expect(connection.Close(context.Background()))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Multi Targets NewPublisher should fail with StateReleased when the destination does not exist", func() {
|
It("Multi Targets NewPublisher should fail with StateReleased when the destination does not exist", func() {
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
publisher, err := connection.NewPublisher(context.Background(), nil, "test")
|
publisher, err := connection.NewPublisher(context.Background(), nil, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
qName := generateNameWithDateTime("Targets NewPublisher should fail when the destination does not exist")
|
qName := generateNameWithDateTime("Targets NewPublisher should fail when the destination does not exist")
|
||||||
|
|
@ -120,11 +121,11 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Multi Targets NewPublisher should success with StateReceived when the destination exists", func() {
|
It("Multi Targets NewPublisher should success with StateReceived when the destination exists", func() {
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
publisher, err := connection.NewPublisher(context.Background(), nil, "test")
|
publisher, err := connection.NewPublisher(context.Background(), nil, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
name := generateNameWithDateTime("Targets NewPublisher should success with StateReceived when the destination exists")
|
name := generateNameWithDateTime("Targets NewPublisher should success with StateReceived when the destination exists")
|
||||||
|
|
@ -146,6 +147,8 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
Name: name,
|
Name: name,
|
||||||
IsAutoDelete: false,
|
IsAutoDelete: false,
|
||||||
})
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
msg = NewMessage([]byte("hello"))
|
msg = NewMessage([]byte("hello"))
|
||||||
Expect(MessagePropertyToAddress(msg, &ExchangeAddress{Exchange: name})).To(BeNil())
|
Expect(MessagePropertyToAddress(msg, &ExchangeAddress{Exchange: name})).To(BeNil())
|
||||||
// the status should be StateReleased since the exchange does not have any binding
|
// the status should be StateReleased since the exchange does not have any binding
|
||||||
|
|
@ -174,10 +177,10 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Multi Targets NewPublisher should fail it TO is not set or not valid", func() {
|
It("Multi Targets NewPublisher should fail it TO is not set or not valid", func() {
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
publisher, err := connection.NewPublisher(context.Background(), nil, "test")
|
publisher, err := connection.NewPublisher(context.Background(), nil, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
msg := NewMessage([]byte("hello"))
|
msg := NewMessage([]byte("hello"))
|
||||||
|
|
@ -200,4 +203,65 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
|
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Message should durable by default", func() {
|
||||||
|
// https://github.com/rabbitmq/rabbitmq-server/pull/13918
|
||||||
|
|
||||||
|
// Here we test the default behavior of the message durability
|
||||||
|
// The lib should set the Header.Durable to true by default
|
||||||
|
// when the Header is set by the user
|
||||||
|
// it is up to the user to set the Header.Durable to true or false
|
||||||
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
name := generateNameWithDateTime("Message should durable by default")
|
||||||
|
_, err = connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: name}, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
|
||||||
|
msg := NewMessage([]byte("hello"))
|
||||||
|
Expect(msg.Header).To(BeNil())
|
||||||
|
publishResult, err := publisher.Publish(context.Background(), msg)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publishResult).NotTo(BeNil())
|
||||||
|
Expect(publishResult.Outcome).To(Equal(&StateAccepted{}))
|
||||||
|
Expect(msg.Header).NotTo(BeNil())
|
||||||
|
Expect(msg.Header.Durable).To(BeTrue())
|
||||||
|
|
||||||
|
consumer, err := connection.NewConsumer(context.Background(), name, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(consumer).NotTo(BeNil())
|
||||||
|
dc, err := consumer.Receive(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(dc).NotTo(BeNil())
|
||||||
|
Expect(dc.Message().Header).NotTo(BeNil())
|
||||||
|
Expect(dc.Message().Header.Durable).To(BeTrue())
|
||||||
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
|
|
||||||
|
msgNotPersistent := NewMessage([]byte("hello"))
|
||||||
|
msgNotPersistent.Header = &amqp.MessageHeader{
|
||||||
|
Durable: false,
|
||||||
|
}
|
||||||
|
publishResult, err = publisher.Publish(context.Background(), msgNotPersistent)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publishResult).NotTo(BeNil())
|
||||||
|
Expect(publishResult.Outcome).To(Equal(&StateAccepted{}))
|
||||||
|
Expect(msgNotPersistent.Header).NotTo(BeNil())
|
||||||
|
Expect(msgNotPersistent.Header.Durable).To(BeFalse())
|
||||||
|
dc, err = consumer.Receive(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(dc).NotTo(BeNil())
|
||||||
|
Expect(dc.Message().Header).NotTo(BeNil())
|
||||||
|
Expect(dc.Message().Header.Durable).To(BeFalse())
|
||||||
|
Expect(dc.Accept(context.Background())).To(BeNil())
|
||||||
|
Expect(publisher.Close(context.Background())).To(BeNil())
|
||||||
|
Expect(connection.Management().DeleteQueue(context.Background(), name)).To(BeNil())
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AmqpQueueInfo struct {
|
type AmqpQueueInfo struct {
|
||||||
name string
|
name string
|
||||||
isDurable bool
|
isDurable bool
|
||||||
isAutoDelete bool
|
isAutoDelete bool
|
||||||
isExclusive bool
|
isExclusive bool
|
||||||
leader string
|
leader string
|
||||||
members []string
|
members []string
|
||||||
arguments map[string]any
|
arguments map[string]any
|
||||||
queueType TQueueType
|
queueType TQueueType
|
||||||
|
consumerCount uint32
|
||||||
|
messageCount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpQueueInfo) Leader() string {
|
func (a *AmqpQueueInfo) Leader() string {
|
||||||
|
|
@ -27,15 +29,22 @@ func (a *AmqpQueueInfo) Members() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAmqpQueueInfo(response map[string]any) *AmqpQueueInfo {
|
func newAmqpQueueInfo(response map[string]any) *AmqpQueueInfo {
|
||||||
|
leader := ""
|
||||||
|
if response["leader"] != nil {
|
||||||
|
leader = response["leader"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
return &AmqpQueueInfo{
|
return &AmqpQueueInfo{
|
||||||
name: response["name"].(string),
|
name: response["name"].(string),
|
||||||
isDurable: response["durable"].(bool),
|
isDurable: response["durable"].(bool),
|
||||||
isAutoDelete: response["auto_delete"].(bool),
|
isAutoDelete: response["auto_delete"].(bool),
|
||||||
isExclusive: response["exclusive"].(bool),
|
isExclusive: response["exclusive"].(bool),
|
||||||
queueType: TQueueType(response["type"].(string)),
|
queueType: TQueueType(response["type"].(string)),
|
||||||
leader: response["leader"].(string),
|
leader: leader,
|
||||||
members: response["replicas"].([]string),
|
members: response["replicas"].([]string),
|
||||||
arguments: response["arguments"].(map[string]any),
|
arguments: response["arguments"].(map[string]any),
|
||||||
|
consumerCount: response["consumer_count"].(uint32),
|
||||||
|
messageCount: response["message_count"].(uint64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,6 +72,14 @@ func (a *AmqpQueueInfo) Arguments() map[string]any {
|
||||||
return a.arguments
|
return a.arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AmqpQueueInfo) ConsumerCount() uint32 {
|
||||||
|
return a.consumerCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AmqpQueueInfo) MessageCount() uint64 {
|
||||||
|
return a.messageCount
|
||||||
|
}
|
||||||
|
|
||||||
type AmqpQueue struct {
|
type AmqpQueue struct {
|
||||||
management *AmqpManagement
|
management *AmqpManagement
|
||||||
arguments map[string]any
|
arguments map[string]any
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
var connection *AmqpConnection
|
var connection *AmqpConnection
|
||||||
var management *AmqpManagement
|
var management *AmqpManagement
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
conn, err := Dial(context.TODO(), []string{"amqp://"}, nil)
|
conn, err := Dial(context.TODO(), "amqp://", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
connection = conn
|
connection = conn
|
||||||
management = connection.Management()
|
management = connection.Management()
|
||||||
|
|
@ -37,6 +37,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
|
|
||||||
// validate GET (query queue info)
|
// validate GET (query queue info)
|
||||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfoReceived).To(Equal(queueInfo))
|
Expect(queueInfoReceived).To(Equal(queueInfo))
|
||||||
|
|
||||||
err = management.DeleteQueue(context.TODO(), queueName)
|
err = management.DeleteQueue(context.TODO(), queueName)
|
||||||
|
|
@ -68,6 +69,8 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
Expect(queueInfo.IsAutoDelete()).To(BeTrue())
|
Expect(queueInfo.IsAutoDelete()).To(BeTrue())
|
||||||
Expect(queueInfo.IsExclusive()).To(BeTrue())
|
Expect(queueInfo.IsExclusive()).To(BeTrue())
|
||||||
Expect(queueInfo.Type()).To(Equal(Classic))
|
Expect(queueInfo.Type()).To(Equal(Classic))
|
||||||
|
Expect(queueInfo.messageCount).To(BeZero())
|
||||||
|
Expect(queueInfo.consumerCount).To(BeZero())
|
||||||
Expect(queueInfo.Leader()).To(ContainSubstring("rabbit"))
|
Expect(queueInfo.Leader()).To(ContainSubstring("rabbit"))
|
||||||
Expect(len(queueInfo.Members())).To(BeNumerically(">", 0))
|
Expect(len(queueInfo.Members())).To(BeNumerically(">", 0))
|
||||||
|
|
||||||
|
|
@ -84,6 +87,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
|
|
||||||
// validate GET (query queue info)
|
// validate GET (query queue info)
|
||||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfoReceived).To(Equal(queueInfo))
|
Expect(queueInfoReceived).To(Equal(queueInfo))
|
||||||
|
|
||||||
err = management.DeleteQueue(context.TODO(), queueName)
|
err = management.DeleteQueue(context.TODO(), queueName)
|
||||||
|
|
@ -108,6 +112,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
Expect(queueInfo.Type()).To(Equal(Quorum))
|
Expect(queueInfo.Type()).To(Equal(Quorum))
|
||||||
// validate GET (query queue info)
|
// validate GET (query queue info)
|
||||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfoReceived).To(Equal(queueInfo))
|
Expect(queueInfoReceived).To(Equal(queueInfo))
|
||||||
|
|
||||||
err = management.DeleteQueue(context.TODO(), queueName)
|
err = management.DeleteQueue(context.TODO(), queueName)
|
||||||
|
|
@ -134,6 +139,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
Expect(queueInfo.Type()).To(Equal(Classic))
|
Expect(queueInfo.Type()).To(Equal(Classic))
|
||||||
// validate GET (query queue info)
|
// validate GET (query queue info)
|
||||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfoReceived).To(Equal(queueInfo))
|
Expect(queueInfoReceived).To(Equal(queueInfo))
|
||||||
|
|
||||||
err = management.DeleteQueue(context.TODO(), queueName)
|
err = management.DeleteQueue(context.TODO(), queueName)
|
||||||
|
|
@ -238,10 +244,10 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
func publishMessages(queueName string, count int, args ...string) {
|
func publishMessages(queueName string, count int, args ...string) {
|
||||||
conn, err := Dial(context.TODO(), []string{"amqp://guest:guest@localhost"}, nil)
|
conn, err := Dial(context.TODO(), "amqp://guest:guest@localhost", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
publisher, err := conn.NewPublisher(context.TODO(), &QueueAddress{Queue: queueName}, "test")
|
publisher, err := conn.NewPublisher(context.TODO(), &QueueAddress{Queue: queueName}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package rabbitmqamqp
|
package rabbitmqamqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
@ -13,20 +14,20 @@ type StateRejected = amqp.StateRejected
|
||||||
type StateReleased = amqp.StateReleased
|
type StateReleased = amqp.StateReleased
|
||||||
type StateModified = amqp.StateModified
|
type StateModified = amqp.StateModified
|
||||||
|
|
||||||
type linkerName interface {
|
type iLinkerName interface {
|
||||||
linkName() string
|
linkName() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLinkName(l linkerName) string {
|
func getLinkName(l iLinkerName) string {
|
||||||
if l == nil || l.linkName() == "" {
|
if l == nil || l.linkName() == "" {
|
||||||
return uuid.New().String()
|
return uuid.New().String()
|
||||||
}
|
}
|
||||||
return l.linkName()
|
return l.linkName()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ConsumerOptions interface for the AMQP and Stream consumer///
|
/// IConsumerOptions interface for the AMQP and Stream consumer///
|
||||||
|
|
||||||
type ConsumerOptions interface {
|
type IConsumerOptions interface {
|
||||||
// linkName returns the name of the link
|
// linkName returns the name of the link
|
||||||
// if not set it will return a random UUID
|
// if not set it will return a random UUID
|
||||||
linkName() string
|
linkName() string
|
||||||
|
|
@ -37,16 +38,22 @@ type ConsumerOptions interface {
|
||||||
// linkFilters returns the link filters for the link.
|
// linkFilters returns the link filters for the link.
|
||||||
// It is mostly used for the stream consumers.
|
// It is mostly used for the stream consumers.
|
||||||
linkFilters() []amqp.LinkFilter
|
linkFilters() []amqp.LinkFilter
|
||||||
|
|
||||||
|
// id returns the id of the consumer
|
||||||
|
id() string
|
||||||
|
|
||||||
|
// validate the consumer options based on the available features
|
||||||
|
validate(available *featuresAvailable) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInitialCredits(co ConsumerOptions) int32 {
|
func getInitialCredits(co IConsumerOptions) int32 {
|
||||||
if co == nil || co.initialCredits() == 0 {
|
if co == nil || co.initialCredits() == 0 {
|
||||||
return 256
|
return 256
|
||||||
}
|
}
|
||||||
return co.initialCredits()
|
return co.initialCredits()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLinkFilters(co ConsumerOptions) []amqp.LinkFilter {
|
func getLinkFilters(co IConsumerOptions) []amqp.LinkFilter {
|
||||||
if co == nil {
|
if co == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -69,32 +76,54 @@ func (mo *managementOptions) linkFilters() []amqp.LinkFilter {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AMQPConsumerOptions struct {
|
func (mo *managementOptions) id() string {
|
||||||
//ReceiverLinkName: see the ConsumerOptions interface
|
return "management"
|
||||||
ReceiverLinkName string
|
|
||||||
//InitialCredits: see the ConsumerOptions interface
|
|
||||||
InitialCredits int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aco *AMQPConsumerOptions) linkName() string {
|
func (mo *managementOptions) validate(available *featuresAvailable) error {
|
||||||
return aco.ReceiverLinkName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aco *AMQPConsumerOptions) initialCredits() int32 {
|
|
||||||
return aco.InitialCredits
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aco *AMQPConsumerOptions) linkFilters() []amqp.LinkFilter {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type OffsetSpecification interface {
|
// ConsumerOptions represents the options for quorum and classic queues
|
||||||
|
type ConsumerOptions struct {
|
||||||
|
//ReceiverLinkName: see the IConsumerOptions interface
|
||||||
|
ReceiverLinkName string
|
||||||
|
//InitialCredits: see the IConsumerOptions interface
|
||||||
|
InitialCredits int32
|
||||||
|
// The id of the consumer
|
||||||
|
Id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aco *ConsumerOptions) linkName() string {
|
||||||
|
return aco.ReceiverLinkName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aco *ConsumerOptions) initialCredits() int32 {
|
||||||
|
return aco.InitialCredits
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aco *ConsumerOptions) linkFilters() []amqp.LinkFilter {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aco *ConsumerOptions) id() string {
|
||||||
|
return aco.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aco *ConsumerOptions) validate(available *featuresAvailable) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IOffsetSpecification interface {
|
||||||
toLinkFilter() amqp.LinkFilter
|
toLinkFilter() amqp.LinkFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
const rmqStreamFilter = "rabbitmq:stream-filter"
|
const rmqStreamFilter = "rabbitmq:stream-filter"
|
||||||
const rmqStreamOffsetSpec = "rabbitmq:stream-offset-spec"
|
const rmqStreamOffsetSpec = "rabbitmq:stream-offset-spec"
|
||||||
const rmqStreamMatchUnfiltered = "rabbitmq:stream-match-unfiltered"
|
const rmqStreamMatchUnfiltered = "rabbitmq:stream-match-unfiltered"
|
||||||
|
const amqpApplicationPropertiesFilter = "amqp:application-properties-filter"
|
||||||
|
const amqpPropertiesFilter = "amqp:properties-filter"
|
||||||
|
|
||||||
const offsetFirst = "first"
|
const offsetFirst = "first"
|
||||||
const offsetNext = "next"
|
const offsetNext = "next"
|
||||||
const offsetLast = "last"
|
const offsetLast = "last"
|
||||||
|
|
@ -128,23 +157,37 @@ func (on *OffsetNext) toLinkFilter() amqp.LinkFilter {
|
||||||
return amqp.NewLinkFilter(rmqStreamOffsetSpec, 0, offsetNext)
|
return amqp.NewLinkFilter(rmqStreamOffsetSpec, 0, offsetNext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamFilterOptions represents the options that can be used to filter the stream data.
|
||||||
|
// It is used in the StreamConsumerOptions.
|
||||||
|
// See: https://www.rabbitmq.com/blog/2024/12/13/amqp-filter-expressions/
|
||||||
|
type StreamFilterOptions struct {
|
||||||
|
// Filter values.
|
||||||
|
Values []string
|
||||||
|
//
|
||||||
|
MatchUnfiltered bool
|
||||||
|
|
||||||
|
// Filter the data based on Application Property
|
||||||
|
ApplicationProperties map[string]any
|
||||||
|
|
||||||
|
// Filter the data based on Message Properties
|
||||||
|
Properties *amqp.MessageProperties
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
StreamConsumerOptions represents the options that can be used to create a stream consumer.
|
StreamConsumerOptions represents the options for stream queues
|
||||||
It is mandatory in case of creating a stream consumer.
|
It is mandatory in case of creating a stream consumer.
|
||||||
*/
|
*/
|
||||||
type StreamConsumerOptions struct {
|
type StreamConsumerOptions struct {
|
||||||
//ReceiverLinkName: see the ConsumerOptions interface
|
//ReceiverLinkName: see the IConsumerOptions interface
|
||||||
ReceiverLinkName string
|
ReceiverLinkName string
|
||||||
//InitialCredits: see the ConsumerOptions interface
|
//InitialCredits: see the IConsumerOptions interface
|
||||||
InitialCredits int32
|
InitialCredits int32
|
||||||
// The offset specification for the stream consumer
|
// The offset specification for the stream consumer
|
||||||
// see the interface implementations
|
// see the interface implementations
|
||||||
Offset OffsetSpecification
|
Offset IOffsetSpecification
|
||||||
// Filter values.
|
StreamFilterOptions *StreamFilterOptions
|
||||||
// See: https://www.rabbitmq.com/blog/2024/12/13/amqp-filter-expressions for more details
|
|
||||||
Filters []string
|
Id string
|
||||||
//
|
|
||||||
FilterMatchUnfiltered bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sco *StreamConsumerOptions) linkName() string {
|
func (sco *StreamConsumerOptions) linkName() string {
|
||||||
|
|
@ -157,29 +200,117 @@ func (sco *StreamConsumerOptions) initialCredits() int32 {
|
||||||
|
|
||||||
func (sco *StreamConsumerOptions) linkFilters() []amqp.LinkFilter {
|
func (sco *StreamConsumerOptions) linkFilters() []amqp.LinkFilter {
|
||||||
var filters []amqp.LinkFilter
|
var filters []amqp.LinkFilter
|
||||||
|
|
||||||
filters = append(filters, sco.Offset.toLinkFilter())
|
filters = append(filters, sco.Offset.toLinkFilter())
|
||||||
if sco.Filters != nil {
|
if sco.StreamFilterOptions != nil && sco.StreamFilterOptions.Values != nil {
|
||||||
l := []any{}
|
var l []any
|
||||||
for _, f := range sco.Filters {
|
for _, f := range sco.StreamFilterOptions.Values {
|
||||||
l = append(l, f)
|
l = append(l, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
filters = append(filters, amqp.NewLinkFilter(rmqStreamFilter, 0, l))
|
filters = append(filters, amqp.NewLinkFilter(rmqStreamFilter, 0, l))
|
||||||
filters = append(filters, amqp.NewLinkFilter(rmqStreamMatchUnfiltered, 0, sco.FilterMatchUnfiltered))
|
filters = append(filters, amqp.NewLinkFilter(rmqStreamMatchUnfiltered, 0, sco.StreamFilterOptions.MatchUnfiltered))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions != nil && sco.StreamFilterOptions.ApplicationProperties != nil {
|
||||||
|
l := map[string]any{}
|
||||||
|
for k, v := range sco.StreamFilterOptions.ApplicationProperties {
|
||||||
|
l[k] = v
|
||||||
|
}
|
||||||
|
filters = append(filters, amqp.NewLinkFilter(amqpApplicationPropertiesFilter, 0, l))
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions != nil && sco.StreamFilterOptions.Properties != nil {
|
||||||
|
l := map[amqp.Symbol]any{}
|
||||||
|
if sco.StreamFilterOptions.Properties.ContentType != nil {
|
||||||
|
l["content-type"] = amqp.Symbol(*sco.StreamFilterOptions.Properties.ContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.ContentEncoding != nil {
|
||||||
|
l["content-encoding"] = amqp.Symbol(*sco.StreamFilterOptions.Properties.ContentEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.CorrelationID != nil {
|
||||||
|
l["correlation-id"] = sco.StreamFilterOptions.Properties.CorrelationID
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.MessageID != nil {
|
||||||
|
l["message-id"] = sco.StreamFilterOptions.Properties.MessageID
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.Subject != nil {
|
||||||
|
l["subject"] = *sco.StreamFilterOptions.Properties.Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.ReplyTo != nil {
|
||||||
|
l["reply-to"] = *sco.StreamFilterOptions.Properties.ReplyTo
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.To != nil {
|
||||||
|
l["to"] = *sco.StreamFilterOptions.Properties.To
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.GroupID != nil {
|
||||||
|
l["group-id"] = *sco.StreamFilterOptions.Properties.GroupID
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.UserID != nil {
|
||||||
|
l["user-id"] = sco.StreamFilterOptions.Properties.UserID
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.AbsoluteExpiryTime != nil {
|
||||||
|
l["absolute-expiry-time"] = sco.StreamFilterOptions.Properties.AbsoluteExpiryTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.CreationTime != nil {
|
||||||
|
l["creation-time"] = sco.StreamFilterOptions.Properties.CreationTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.GroupSequence != nil {
|
||||||
|
l["group-sequence"] = *sco.StreamFilterOptions.Properties.GroupSequence
|
||||||
|
}
|
||||||
|
|
||||||
|
if sco.StreamFilterOptions.Properties.ReplyToGroupID != nil {
|
||||||
|
l["reply-to-group-id"] = *sco.StreamFilterOptions.Properties.ReplyToGroupID
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l) > 0 {
|
||||||
|
filters = append(filters, amqp.NewLinkFilter(amqpPropertiesFilter, 0, l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return filters
|
return filters
|
||||||
}
|
}
|
||||||
|
|
||||||
///// ProducerOptions /////
|
func (sco *StreamConsumerOptions) id() string {
|
||||||
|
return sco.Id
|
||||||
type ProducerOptions interface {
|
|
||||||
linkName() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AMQPProducerOptions struct {
|
func (sco *StreamConsumerOptions) validate(available *featuresAvailable) error {
|
||||||
|
if sco.StreamFilterOptions != nil && sco.StreamFilterOptions.Properties != nil {
|
||||||
|
if !available.is41OrMore {
|
||||||
|
return fmt.Errorf("stream consumer with properties filter is not supported. You need RabbitMQ 4.1 or later")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
///// PublisherOptions /////
|
||||||
|
|
||||||
|
type IPublisherOptions interface {
|
||||||
|
linkName() string
|
||||||
|
id() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PublisherOptions struct {
|
||||||
|
Id string
|
||||||
SenderLinkName string
|
SenderLinkName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (apo *AMQPProducerOptions) linkName() string {
|
func (apo *PublisherOptions) linkName() string {
|
||||||
return apo.SenderLinkName
|
return apo.SenderLinkName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (apo *PublisherOptions) id() string {
|
||||||
|
return apo.Id
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func createSenderLinkOptions(address string, linkName string, deliveryMode int)
|
||||||
// receiverLinkOptions returns the options for a receiver link
|
// receiverLinkOptions returns the options for a receiver link
|
||||||
// with the given address and link name.
|
// with the given address and link name.
|
||||||
// That should be the same for all the links.
|
// That should be the same for all the links.
|
||||||
func createReceiverLinkOptions(address string, options ConsumerOptions, deliveryMode int) *amqp.ReceiverOptions {
|
func createReceiverLinkOptions(address string, options IConsumerOptions, deliveryMode int) *amqp.ReceiverOptions {
|
||||||
prop := make(map[string]any)
|
prop := make(map[string]any)
|
||||||
prop["paired"] = true
|
prop["paired"] = true
|
||||||
receiverSettleMode := amqp.SenderSettleModeSettled.Ptr()
|
receiverSettleMode := amqp.SenderSettleModeSettled.Ptr()
|
||||||
|
|
@ -74,7 +74,7 @@ func random(max int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateMessageAnnotations(annotations amqp.Annotations) error {
|
func validateMessageAnnotations(annotations amqp.Annotations) error {
|
||||||
for k, _ := range annotations {
|
for k := range annotations {
|
||||||
switch tp := k.(type) {
|
switch tp := k.(type) {
|
||||||
case string:
|
case string:
|
||||||
if err := validateMessageAnnotationKey(tp); err != nil {
|
if err := validateMessageAnnotationKey(tp); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ const (
|
||||||
key = "key"
|
key = "key"
|
||||||
queues = "queues"
|
queues = "queues"
|
||||||
bindings = "bindings"
|
bindings = "bindings"
|
||||||
|
authTokens = "/auth/tokens"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validatePositive(label string, value int64) error {
|
func validatePositive(label string, value int64) error {
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,15 @@ var _ = Describe("Converters", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Converts from string logError", func() {
|
It("Converts from string logError", func() {
|
||||||
v, err := CapacityFrom("10LL")
|
_, err := CapacityFrom("10LL")
|
||||||
Expect(fmt.Sprintf("%s", err)).
|
Expect(fmt.Sprintf("%s", err)).
|
||||||
To(ContainSubstring("Invalid unit size format"))
|
To(ContainSubstring("Invalid unit size format"))
|
||||||
|
|
||||||
v, err = CapacityFrom("aGB")
|
_, err = CapacityFrom("aGB")
|
||||||
Expect(fmt.Sprintf("%s", err)).
|
Expect(fmt.Sprintf("%s", err)).
|
||||||
To(ContainSubstring("Invalid number format"))
|
To(ContainSubstring("Invalid number format"))
|
||||||
|
|
||||||
v, err = CapacityFrom("")
|
v, err := CapacityFrom("")
|
||||||
Expect(v).To(Equal(int64(0)))
|
Expect(v).To(Equal(int64(0)))
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package rabbitmqamqp
|
package rabbitmqamqp
|
||||||
|
|
||||||
type entityIdentifier interface {
|
type iEntityIdentifier interface {
|
||||||
Id() string
|
Id() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,9 +21,9 @@ func (e QueueType) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
QueueSpecification represents the specification of a queue
|
IQueueSpecification represents the specification of a queue
|
||||||
*/
|
*/
|
||||||
type QueueSpecification interface {
|
type IQueueSpecification interface {
|
||||||
name() string
|
name() string
|
||||||
isAutoDelete() bool
|
isAutoDelete() bool
|
||||||
isExclusive() bool
|
isExclusive() bool
|
||||||
|
|
@ -31,7 +31,7 @@ type QueueSpecification interface {
|
||||||
buildArguments() map[string]any
|
buildArguments() map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
type OverflowStrategy interface {
|
type IOverflowStrategy interface {
|
||||||
overflowStrategy() string
|
overflowStrategy() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ func (r *RejectPublishDlxOverflowStrategy) overflowStrategy() string {
|
||||||
return "reject-publish-dlx"
|
return "reject-publish-dlx"
|
||||||
}
|
}
|
||||||
|
|
||||||
type LeaderLocator interface {
|
type ILeaderLocator interface {
|
||||||
leaderLocator() string
|
leaderLocator() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,18 +79,19 @@ QuorumQueueSpecification represents the specification of the quorum queue
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type QuorumQueueSpecification struct {
|
type QuorumQueueSpecification struct {
|
||||||
Name string
|
Name string
|
||||||
AutoExpire int64
|
AutoExpire int64
|
||||||
MessageTTL int64
|
MessageTTL int64
|
||||||
OverflowStrategy OverflowStrategy
|
OverflowStrategy IOverflowStrategy
|
||||||
SingleActiveConsumer bool
|
SingleActiveConsumer bool
|
||||||
DeadLetterExchange string
|
DeadLetterExchange string
|
||||||
DeadLetterRoutingKey string
|
DeadLetterRoutingKey string
|
||||||
MaxLength int64
|
MaxLength int64
|
||||||
MaxLengthBytes int64
|
MaxLengthBytes int64
|
||||||
DeliveryLimit int64
|
DeliveryLimit int64
|
||||||
TargetClusterSize int64
|
TargetClusterSize int64
|
||||||
LeaderLocator LeaderLocator
|
LeaderLocator ILeaderLocator
|
||||||
|
QuorumInitialGroupSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QuorumQueueSpecification) name() string {
|
func (q *QuorumQueueSpecification) name() string {
|
||||||
|
|
@ -155,6 +156,10 @@ func (q *QuorumQueueSpecification) buildArguments() map[string]any {
|
||||||
result["x-queue-leader-locator"] = q.LeaderLocator.leaderLocator()
|
result["x-queue-leader-locator"] = q.LeaderLocator.leaderLocator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if q.QuorumInitialGroupSize != 0 {
|
||||||
|
result["x-quorum-initial-group-size"] = q.QuorumInitialGroupSize
|
||||||
|
}
|
||||||
|
|
||||||
result["x-queue-type"] = q.queueType().String()
|
result["x-queue-type"] = q.queueType().String()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
@ -168,14 +173,14 @@ type ClassicQueueSpecification struct {
|
||||||
IsExclusive bool
|
IsExclusive bool
|
||||||
AutoExpire int64
|
AutoExpire int64
|
||||||
MessageTTL int64
|
MessageTTL int64
|
||||||
OverflowStrategy OverflowStrategy
|
OverflowStrategy IOverflowStrategy
|
||||||
SingleActiveConsumer bool
|
SingleActiveConsumer bool
|
||||||
DeadLetterExchange string
|
DeadLetterExchange string
|
||||||
DeadLetterRoutingKey string
|
DeadLetterRoutingKey string
|
||||||
MaxLength int64
|
MaxLength int64
|
||||||
MaxLengthBytes int64
|
MaxLengthBytes int64
|
||||||
MaxPriority int64
|
MaxPriority int64
|
||||||
LeaderLocator LeaderLocator
|
LeaderLocator ILeaderLocator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *ClassicQueueSpecification) name() string {
|
func (q *ClassicQueueSpecification) name() string {
|
||||||
|
|
@ -344,8 +349,8 @@ func (e ExchangeType) String() string {
|
||||||
return string(e.Type)
|
return string(e.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExchangeSpecification represents the specification of an exchange
|
// IExchangeSpecification represents the specification of an exchange
|
||||||
type ExchangeSpecification interface {
|
type IExchangeSpecification interface {
|
||||||
name() string
|
name() string
|
||||||
isAutoDelete() bool
|
isAutoDelete() bool
|
||||||
exchangeType() ExchangeType
|
exchangeType() ExchangeType
|
||||||
|
|
@ -465,7 +470,7 @@ func (c *CustomExchangeSpecification) arguments() map[string]any {
|
||||||
|
|
||||||
// / **** Binding ****
|
// / **** Binding ****
|
||||||
|
|
||||||
type BindingSpecification interface {
|
type IBindingSpecification interface {
|
||||||
sourceExchange() string
|
sourceExchange() string
|
||||||
destination() string
|
destination() string
|
||||||
bindingKey() string
|
bindingKey() string
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package rabbitmqamqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -108,6 +109,24 @@ var _ = Describe("Available Features", func() {
|
||||||
Expect(availableFeatures.is4OrMore).To(BeTrue())
|
Expect(availableFeatures.is4OrMore).To(BeTrue())
|
||||||
Expect(availableFeatures.is41OrMore).To(BeTrue())
|
Expect(availableFeatures.is41OrMore).To(BeTrue())
|
||||||
Expect(availableFeatures.isRabbitMQ).To(BeTrue())
|
Expect(availableFeatures.isRabbitMQ).To(BeTrue())
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("StreamConsumerOptions validate for RabbitMQ 4.1", func() {
|
||||||
|
Expect((&StreamConsumerOptions{
|
||||||
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
|
Properties: &amqp.MessageProperties{
|
||||||
|
MessageID: "123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).validate(&featuresAvailable{is41OrMore: false})).To(MatchError("stream consumer with properties filter is not supported. You need RabbitMQ 4.1 or later"))
|
||||||
|
|
||||||
|
Expect((&StreamConsumerOptions{
|
||||||
|
StreamFilterOptions: &StreamFilterOptions{
|
||||||
|
Properties: &amqp.MessageProperties{
|
||||||
|
MessageID: "123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).validate(&featuresAvailable{is41OrMore: true})).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LifeCycleState interface {
|
type ILifeCycleState interface {
|
||||||
getState() int
|
getState() int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ const (
|
||||||
closed = iota
|
closed = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
func statusToString(status LifeCycleState) string {
|
func statusToString(status ILifeCycleState) string {
|
||||||
switch status.getState() {
|
switch status.getState() {
|
||||||
case open:
|
case open:
|
||||||
return "open"
|
return "open"
|
||||||
|
|
@ -65,8 +65,8 @@ func statusToString(status LifeCycleState) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type StateChanged struct {
|
type StateChanged struct {
|
||||||
From LifeCycleState
|
From ILifeCycleState
|
||||||
To LifeCycleState
|
To ILifeCycleState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s StateChanged) String() string {
|
func (s StateChanged) String() string {
|
||||||
|
|
@ -77,6 +77,9 @@ func (s StateChanged) String() string {
|
||||||
|
|
||||||
switch s.To.(type) {
|
switch s.To.(type) {
|
||||||
case *StateClosed:
|
case *StateClosed:
|
||||||
|
if s.To.(*StateClosed).error == nil {
|
||||||
|
return fmt.Sprintf("From: %s, To: %s", statusToString(s.From), statusToString(s.To))
|
||||||
|
}
|
||||||
return fmt.Sprintf("From: %s, To: %s, Error: %s", statusToString(s.From), statusToString(s.To), s.To.(*StateClosed).error)
|
return fmt.Sprintf("From: %s, To: %s, Error: %s", statusToString(s.From), statusToString(s.To), s.To.(*StateClosed).error)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +88,7 @@ func (s StateChanged) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LifeCycle struct {
|
type LifeCycle struct {
|
||||||
state LifeCycleState
|
state ILifeCycleState
|
||||||
chStatusChanged chan *StateChanged
|
chStatusChanged chan *StateChanged
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
@ -97,13 +100,13 @@ func NewLifeCycle() *LifeCycle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) State() LifeCycleState {
|
func (l *LifeCycle) State() ILifeCycleState {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
return l.state
|
return l.state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) SetState(value LifeCycleState) {
|
func (l *LifeCycle) SetState(value ILifeCycleState) {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if l.state == value {
|
if l.state == value {
|
||||||
|
|
@ -122,3 +125,9 @@ func (l *LifeCycle) SetState(value LifeCycleState) {
|
||||||
To: value,
|
To: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LifeCycle) notifyStatusChange(channel chan *StateChanged) {
|
||||||
|
l.mutex.Lock()
|
||||||
|
defer l.mutex.Unlock()
|
||||||
|
l.chStatusChanged = channel
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
// MessagePropertyToAddress sets the To property of the message to the address of the target.
|
// MessagePropertyToAddress sets the To property of the message to the address of the target.
|
||||||
// The target must be a QueueAddress or an ExchangeAddress.
|
// The target must be a QueueAddress or an ExchangeAddress.
|
||||||
// Note: The field msgRef.Properties.To will be overwritten if it is already set.
|
// Note: The field msgRef.Properties.To will be overwritten if it is already set.
|
||||||
func MessagePropertyToAddress(msgRef *amqp.Message, target TargetAddress) error {
|
func MessagePropertyToAddress(msgRef *amqp.Message, target ITargetAddress) error {
|
||||||
if target == nil {
|
if target == nil {
|
||||||
return errors.New("target cannot be nil")
|
return errors.New("target cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ func NewMessage(body []byte) *amqp.Message {
|
||||||
// NewMessageWithAddress creates a new AMQP 1.0 new message with the given payload and sets the To property to the address of the target.
|
// NewMessageWithAddress creates a new AMQP 1.0 new message with the given payload and sets the To property to the address of the target.
|
||||||
// The target must be a QueueAddress or an ExchangeAddress.
|
// The target must be a QueueAddress or an ExchangeAddress.
|
||||||
// This function is a helper that combines NewMessage and MessagePropertyToAddress.
|
// This function is a helper that combines NewMessage and MessagePropertyToAddress.
|
||||||
func NewMessageWithAddress(body []byte, target TargetAddress) (*amqp.Message, error) {
|
func NewMessageWithAddress(body []byte, target ITargetAddress) (*amqp.Message, error) {
|
||||||
message := amqp.NewMessage(body)
|
message := amqp.NewMessage(body)
|
||||||
err := MessagePropertyToAddress(message, target)
|
err := MessagePropertyToAddress(message, target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
package rabbitmqamqp
|
||||||
|
|
||||||
|
// test the OAuth2 connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
testhelper "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/test-helper"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Base64Key = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH"
|
||||||
|
|
||||||
|
// const HmacKey KEY = new HmacKey(Base64.getDecoder().decode(Base64Key));
|
||||||
|
const AUDIENCE = "rabbitmq"
|
||||||
|
|
||||||
|
// Helper function to generate random string
|
||||||
|
func randomString(length int) string {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
result := make([]byte, length)
|
||||||
|
for i := range result {
|
||||||
|
result[i] = charset[rand.Intn(len(charset))]
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("OAuth2 Tests", func() {
|
||||||
|
It("OAuth2 Connection should success", func() {
|
||||||
|
tokenString := token(time.Now().Add(time.Duration(2500) * time.Millisecond))
|
||||||
|
Expect(tokenString).NotTo(BeEmpty())
|
||||||
|
|
||||||
|
conn, err := Dial(context.TODO(), "amqp://localhost:5672",
|
||||||
|
&AmqpConnOptions{
|
||||||
|
ContainerID: "oAuth2Test",
|
||||||
|
OAuth2Options: &OAuth2Options{
|
||||||
|
Token: tokenString,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn).NotTo(BeNil())
|
||||||
|
qName := generateName("OAuth2 Connection should success")
|
||||||
|
_, err = conn.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
|
Name: qName,
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
|
Expect(conn.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("OAuth2 Connection should disconnect after the timeout", func() {
|
||||||
|
|
||||||
|
tokenString := token(time.Now().Add(time.Duration(1_000) * time.Millisecond))
|
||||||
|
Expect(tokenString).NotTo(BeEmpty())
|
||||||
|
|
||||||
|
conn, err := Dial(context.TODO(), "amqp://localhost:5672",
|
||||||
|
&AmqpConnOptions{
|
||||||
|
ContainerID: "oAuth2TestTimeout",
|
||||||
|
OAuth2Options: &OAuth2Options{
|
||||||
|
Token: tokenString,
|
||||||
|
},
|
||||||
|
RecoveryConfiguration: &RecoveryConfiguration{
|
||||||
|
ActiveRecovery: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn).NotTo(BeNil())
|
||||||
|
ch := make(chan *StateChanged, 1)
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
for statusChanged := range ch {
|
||||||
|
x := statusChanged.To.(*StateClosed)
|
||||||
|
Expect(x.GetError()).NotTo(BeNil())
|
||||||
|
Expect(x.GetError().Error()).To(ContainSubstring("credential expired"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
conn.NotifyStatusChange(ch)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("OAuth2 Connection should be alive after token refresh", func() {
|
||||||
|
tokenString := token(time.Now().Add(time.Duration(1) * time.Second))
|
||||||
|
Expect(tokenString).NotTo(BeEmpty())
|
||||||
|
|
||||||
|
conn, err := Dial(context.TODO(), "amqp://localhost:5672",
|
||||||
|
&AmqpConnOptions{
|
||||||
|
ContainerID: "oAuth2Test",
|
||||||
|
OAuth2Options: &OAuth2Options{
|
||||||
|
Token: tokenString,
|
||||||
|
},
|
||||||
|
RecoveryConfiguration: &RecoveryConfiguration{
|
||||||
|
ActiveRecovery: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn).NotTo(BeNil())
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
err = conn.RefreshToken(context.Background(), token(time.Now().Add(time.Duration(2500)*time.Millisecond)))
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(conn.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
// this test is a bit flaky, it may fail if the connection is not closed in time
|
||||||
|
// that should mark as flakes
|
||||||
|
|
||||||
|
It("OAuth2 Connection should use the new token to reconnect", func() {
|
||||||
|
name := "oAuth2TestReconnect_" + time.Now().String()
|
||||||
|
startToken := token(time.Now().Add(time.Duration(1) * time.Second))
|
||||||
|
connection, err := Dial(context.Background(), "amqp://", &AmqpConnOptions{
|
||||||
|
OAuth2Options: &OAuth2Options{
|
||||||
|
Token: startToken,
|
||||||
|
},
|
||||||
|
ContainerID: name,
|
||||||
|
// reduced the reconnect interval to speed up the test
|
||||||
|
RecoveryConfiguration: &RecoveryConfiguration{
|
||||||
|
ActiveRecovery: true,
|
||||||
|
BackOffReconnectInterval: 1100 * time.Millisecond,
|
||||||
|
MaxReconnectAttempts: 5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
ch := make(chan *StateChanged, 1)
|
||||||
|
connection.NotifyStatusChange(ch)
|
||||||
|
newToken := token(time.Now().Add(time.Duration(10) * time.Second))
|
||||||
|
Expect(connection.RefreshToken(context.Background(), newToken)).To(BeNil())
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
// here the token used during the connection (startToken) is expired
|
||||||
|
// the new token should be used to reconnect.
|
||||||
|
// The test is to validate that the client uses the new token to reconnect
|
||||||
|
// The RefreshToken requests a new token and updates the connection with the new token
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := testhelper.DropConnectionContainerID(name)
|
||||||
|
return err == nil
|
||||||
|
}).WithTimeout(5 * time.Second).WithPolling(400 * time.Millisecond).Should(BeTrue())
|
||||||
|
st1 := <-ch
|
||||||
|
Expect(st1.From).To(Equal(&StateOpen{}))
|
||||||
|
Expect(st1.To).To(BeAssignableToTypeOf(&StateClosed{}))
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// the connection should not be reconnected
|
||||||
|
Eventually(func() bool {
|
||||||
|
conn, err := testhelper.GetConnectionByContainerID(name)
|
||||||
|
return err == nil && conn != nil
|
||||||
|
}).WithTimeout(5 * time.Second).WithPolling(400 * time.Millisecond).Should(BeTrue())
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Setting OAuth2 on the Environment should work", func() {
|
||||||
|
env := NewClusterEnvironment([]Endpoint{
|
||||||
|
{Address: "amqp://", Options: &AmqpConnOptions{
|
||||||
|
OAuth2Options: &OAuth2Options{
|
||||||
|
Token: token(time.Now().Add(time.Duration(10) * time.Second)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}})
|
||||||
|
|
||||||
|
Expect(env).NotTo(BeNil())
|
||||||
|
Expect(env.Connections()).NotTo(BeNil())
|
||||||
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
|
connection, err := env.NewConnection(context.Background())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
Expect(len(env.Connections())).To(Equal(1))
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
Expect(len(env.Connections())).To(Equal(0))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Can't use refresh token if not OAuth2 is enabled ", func() {
|
||||||
|
connection, err := Dial(context.Background(), "amqp://", nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
err = connection.RefreshToken(context.Background(), token(time.Now().Add(time.Duration(10)*time.Second)))
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
Expect(err.Error()).To(ContainSubstring("is not configured to use OAuth2 token"))
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
func token(duration time.Time) string {
|
||||||
|
decodedKey, _ := base64.StdEncoding.DecodeString(Base64Key)
|
||||||
|
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"iss": "unit_test",
|
||||||
|
"aud": AUDIENCE,
|
||||||
|
"exp": jwt.NewNumericDate(duration),
|
||||||
|
"scope": []string{"rabbitmq.configure:*/*", "rabbitmq.write:*/*", "rabbitmq.read:*/*"},
|
||||||
|
"random": randomString(6),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new token object, specifying signing method and the claims
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
token.Header["kid"] = "token-key"
|
||||||
|
|
||||||
|
// Sign and get the complete encoded token as a string using the secret
|
||||||
|
tokenString, err := token.SignedString(decodedKey)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
return tokenString
|
||||||
|
}
|
||||||
|
|
@ -8,5 +8,30 @@ import (
|
||||||
|
|
||||||
func generateNameWithDateTime(name string) string {
|
func generateNameWithDateTime(name string) string {
|
||||||
return fmt.Sprintf("%s_%s", name, strconv.FormatInt(time.Now().Unix(), 10))
|
return fmt.Sprintf("%s_%s", name, strconv.FormatInt(time.Now().Unix(), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create string pointers
|
||||||
|
func stringPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func uint32Ptr(i uint32) *uint32 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a static date time string for testing
|
||||||
|
|
||||||
|
func createDateTime() time.Time {
|
||||||
|
layout := time.RFC3339
|
||||||
|
value := "2006-01-02T15:04:05Z"
|
||||||
|
t, err := time.Parse(layout, value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert time to pointer
|
||||||
|
func timePtr(t time.Time) *time.Time {
|
||||||
|
return &t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ func DropConnectionContainerID(Id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,6 +73,7 @@ func DropConnection(name string, port string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpGet(url, username, password string) (string, error) {
|
func httpGet(url, username, password string) (string, error) {
|
||||||
return baseCall(url, username, password, "GET")
|
return baseCall(url, username, password, "GET")
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +106,11 @@ func baseCall(url, username, password string, method string) (string, error) {
|
||||||
return string(bodyBytes), nil
|
return string(bodyBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == 201 {
|
||||||
|
// Created! it is ok
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode == 204 { // No Content
|
if resp.StatusCode == 204 { // No Content
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue