2025-02-17 21:04:13 +08:00
|
|
|
package rabbitmqamqp
|
2024-09-10 17:26:46 +08:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2024-10-02 06:25:12 +08:00
|
|
|
"github.com/Azure/go-amqp"
|
|
|
|
|
"github.com/google/uuid"
|
2024-11-21 17:34:08 +08:00
|
|
|
"strconv"
|
|
|
|
|
"time"
|
2024-09-10 17:26:46 +08:00
|
|
|
)
|
|
|
|
|
|
2024-09-18 00:59:33 +08:00
|
|
|
var ErrPreconditionFailed = errors.New("precondition Failed")
|
2024-10-02 06:25:12 +08:00
|
|
|
var ErrDoesNotExist = errors.New("does not exist")
|
2024-09-10 17:26:46 +08:00
|
|
|
|
2025-02-07 18:00:14 +08:00
|
|
|
/*
|
|
|
|
|
AmqpManagement is the interface to the RabbitMQ /management endpoint
|
|
|
|
|
The management interface is used to declare/delete exchanges, queues, and bindings
|
|
|
|
|
*/
|
2024-09-10 17:26:46 +08:00
|
|
|
type AmqpManagement struct {
|
|
|
|
|
session *amqp.Session
|
|
|
|
|
sender *amqp.Sender
|
|
|
|
|
receiver *amqp.Receiver
|
|
|
|
|
lifeCycle *LifeCycle
|
|
|
|
|
cancel context.CancelFunc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewAmqpManagement() *AmqpManagement {
|
|
|
|
|
return &AmqpManagement{
|
|
|
|
|
lifeCycle: NewLifeCycle(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AmqpManagement) ensureReceiverLink(ctx context.Context) error {
|
2025-02-13 19:04:35 +08:00
|
|
|
opts := createReceiverLinkOptions(managementNodeAddress, &managementOptions{}, AtMostOnce)
|
2025-02-07 18:00:14 +08:00
|
|
|
receiver, err := a.session.NewReceiver(ctx, managementNodeAddress, opts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2024-09-10 17:26:46 +08:00
|
|
|
}
|
2025-02-07 18:00:14 +08:00
|
|
|
a.receiver = receiver
|
2024-09-10 17:26:46 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AmqpManagement) ensureSenderLink(ctx context.Context) error {
|
2025-02-07 18:00:14 +08:00
|
|
|
sender, err := a.session.NewSender(ctx, managementNodeAddress,
|
|
|
|
|
createSenderLinkOptions(managementNodeAddress, linkPairName, AtMostOnce))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2024-09-10 17:26:46 +08:00
|
|
|
}
|
2025-02-07 18:00:14 +08:00
|
|
|
|
|
|
|
|
a.sender = sender
|
2024-09-10 17:26:46 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 22:26:12 +08:00
|
|
|
func (a *AmqpManagement) Open(ctx context.Context, connection *AmqpConnection) error {
|
2025-02-07 18:00:14 +08:00
|
|
|
session, err := connection.azureConnection.NewSession(ctx, nil)
|
2024-09-10 17:26:46 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
a.session = session
|
|
|
|
|
err = a.ensureSenderLink(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = a.ensureReceiverLink(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
|
// Even 10ms is enough to allow the links to establish,
|
|
|
|
|
// which tells me it allows the golang runtime to process
|
|
|
|
|
// some channels or I/O or something elsewhere
|
|
|
|
|
time.Sleep(time.Millisecond * 10)
|
|
|
|
|
|
2025-01-16 22:26:12 +08:00
|
|
|
a.lifeCycle.SetState(&StateOpen{})
|
2024-09-10 17:26:46 +08:00
|
|
|
return ctx.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AmqpManagement) Close(ctx context.Context) error {
|
|
|
|
|
_ = a.sender.Close(ctx)
|
|
|
|
|
_ = a.receiver.Close(ctx)
|
|
|
|
|
err := a.session.Close(ctx)
|
2025-01-16 22:26:12 +08:00
|
|
|
a.lifeCycle.SetState(&StateClosed{})
|
2024-09-10 17:26:46 +08:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-07 18:00:14 +08:00
|
|
|
/*
|
|
|
|
|
Request sends a request to the /management endpoint.
|
|
|
|
|
It is a generic method that can be used to send any request to the management endpoint.
|
|
|
|
|
In most of the cases you don't need to use this method directly, instead use the standard methods
|
|
|
|
|
*/
|
2024-09-10 17:26:46 +08:00
|
|
|
func (a *AmqpManagement) Request(ctx context.Context, body any, path string, method string,
|
|
|
|
|
expectedResponseCodes []int) (map[string]any, error) {
|
|
|
|
|
return a.request(ctx, uuid.New().String(), body, path, method, expectedResponseCodes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AmqpManagement) validateResponseCode(responseCode int, expectedResponseCodes []int) error {
|
2024-09-11 20:42:05 +08:00
|
|
|
if responseCode == responseCode409 {
|
2024-09-18 00:59:33 +08:00
|
|
|
return ErrPreconditionFailed
|
2024-09-11 20:42:05 +08:00
|
|
|
}
|
|
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
for _, code := range expectedResponseCodes {
|
|
|
|
|
if code == responseCode {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-11 20:42:05 +08:00
|
|
|
|
2024-09-18 00:59:33 +08:00
|
|
|
return fmt.Errorf("expected response code %d got %d", expectedResponseCodes, responseCode)
|
2024-09-10 17:26:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AmqpManagement) request(ctx context.Context, id string, body any, path string, method string,
|
|
|
|
|
expectedResponseCodes []int) (map[string]any, error) {
|
2024-09-16 15:34:27 +08:00
|
|
|
amqpMessage := &amqp.Message{
|
|
|
|
|
Value: body,
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
s := commandReplyTo
|
|
|
|
|
amqpMessage.Properties = &amqp.MessageProperties{
|
|
|
|
|
ReplyTo: &s,
|
|
|
|
|
To: &path,
|
|
|
|
|
Subject: &method,
|
|
|
|
|
MessageID: &id,
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
opts := &amqp.SendOptions{Settled: true}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
err := a.sender.Send(ctx, amqpMessage, opts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return make(map[string]any), err
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
msg, err := a.receiver.Receive(ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return make(map[string]any), err
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
err = a.receiver.AcceptMessage(ctx, msg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
if msg.Properties == nil {
|
|
|
|
|
return make(map[string]any), fmt.Errorf("expected properties in the message")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if msg.Properties.CorrelationID == nil {
|
|
|
|
|
return make(map[string]any), fmt.Errorf("expected correlation id in the message")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if msg.Properties.CorrelationID != id {
|
|
|
|
|
return make(map[string]any), fmt.Errorf("expected correlation id %s got %s", id, msg.Properties.CorrelationID)
|
|
|
|
|
}
|
2024-09-18 00:59:33 +08:00
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
switch msg.Value.(type) {
|
|
|
|
|
case map[string]interface{}:
|
|
|
|
|
return msg.Value.(map[string]any), nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-02 06:25:12 +08:00
|
|
|
responseCode, _ := strconv.Atoi(*msg.Properties.Subject)
|
2024-09-10 17:26:46 +08:00
|
|
|
|
2024-10-02 06:25:12 +08:00
|
|
|
err = a.validateResponseCode(responseCode, expectedResponseCodes)
|
2024-09-10 17:26:46 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-02 06:25:12 +08:00
|
|
|
if responseCode == responseCode404 {
|
|
|
|
|
return nil, ErrDoesNotExist
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 17:26:46 +08:00
|
|
|
return make(map[string]any), nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification QueueSpecification) (*AmqpQueueInfo, error) {
|
|
|
|
|
if specification == nil {
|
|
|
|
|
return nil, fmt.Errorf("queue specification cannot be nil. You need to provide a valid QueueSpecification")
|
2024-11-15 15:37:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
amqpQueue := newAmqpQueue(a, specification.name())
|
|
|
|
|
amqpQueue.AutoDelete(specification.isAutoDelete())
|
|
|
|
|
amqpQueue.Exclusive(specification.isExclusive())
|
|
|
|
|
amqpQueue.QueueType(specification.queueType())
|
|
|
|
|
amqpQueue.SetArguments(specification.buildArguments())
|
|
|
|
|
|
2024-11-15 15:37:28 +08:00
|
|
|
return amqpQueue.Declare(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AmqpManagement) DeleteQueue(ctx context.Context, name string) error {
|
|
|
|
|
q := newAmqpQueue(a, name)
|
|
|
|
|
return q.Delete(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
func (a *AmqpManagement) DeclareExchange(ctx context.Context, exchangeSpecification ExchangeSpecification) (*AmqpExchangeInfo, error) {
|
2024-11-15 15:37:28 +08:00
|
|
|
if exchangeSpecification == nil {
|
2025-01-30 18:29:44 +08:00
|
|
|
return nil, errors.New("exchange specification cannot be nil. You need to provide a valid ExchangeSpecification")
|
2024-11-15 15:37:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
exchange := newAmqpExchange(a, exchangeSpecification.name())
|
|
|
|
|
exchange.AutoDelete(exchangeSpecification.isAutoDelete())
|
|
|
|
|
exchange.ExchangeType(exchangeSpecification.exchangeType())
|
2024-11-15 15:37:28 +08:00
|
|
|
return exchange.Declare(ctx)
|
2024-09-10 17:26:46 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-15 15:37:28 +08:00
|
|
|
func (a *AmqpManagement) DeleteExchange(ctx context.Context, name string) error {
|
|
|
|
|
e := newAmqpExchange(a, name)
|
|
|
|
|
return e.Delete(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
func (a *AmqpManagement) Bind(ctx context.Context, bindingSpecification BindingSpecification) (string, error) {
|
|
|
|
|
if bindingSpecification == nil {
|
|
|
|
|
return "", fmt.Errorf("binding specification cannot be nil. You need to provide a valid BindingSpecification")
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-15 15:37:28 +08:00
|
|
|
bind := newAMQPBinding(a)
|
2025-01-30 18:29:44 +08:00
|
|
|
bind.SourceExchange(bindingSpecification.sourceExchange())
|
|
|
|
|
bind.Destination(bindingSpecification.destination(), bindingSpecification.isDestinationQueue())
|
|
|
|
|
bind.BindingKey(bindingSpecification.bindingKey())
|
2024-11-15 15:37:28 +08:00
|
|
|
return bind.Bind(ctx)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
func (a *AmqpManagement) Unbind(ctx context.Context, bindingPath string) error {
|
|
|
|
|
bind := newAMQPBinding(a)
|
|
|
|
|
return bind.Unbind(ctx, bindingPath)
|
|
|
|
|
}
|
2025-01-16 22:26:12 +08:00
|
|
|
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*AmqpQueueInfo, error) {
|
2025-01-30 18:29:44 +08:00
|
|
|
path, err := queueAddress(&queueName)
|
2024-11-15 15:37:28 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-10-02 06:25:12 +08:00
|
|
|
result, err := a.Request(ctx, amqp.Null{}, path, commandGet, []int{responseCode200, responseCode404})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return newAmqpQueueInfo(result), nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-15 15:37:28 +08:00
|
|
|
func (a *AmqpManagement) PurgeQueue(ctx context.Context, queueName string) (int, error) {
|
|
|
|
|
purge := newAmqpQueue(a, queueName)
|
|
|
|
|
return purge.Purge(ctx)
|
2024-09-10 17:26:46 +08:00
|
|
|
}
|
|
|
|
|
|
2025-01-16 22:26:12 +08:00
|
|
|
func (a *AmqpManagement) NotifyStatusChange(channel chan *StateChanged) {
|
2024-09-10 17:26:46 +08:00
|
|
|
a.lifeCycle.chStatusChanged = channel
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 22:26:12 +08:00
|
|
|
func (a *AmqpManagement) State() LifeCycleState {
|
|
|
|
|
return a.lifeCycle.State()
|
2024-09-10 17:26:46 +08:00
|
|
|
}
|