Add multi uris support (#17)
* Multi uris configuration * remove the Interfaces * Add outcome delivery * refactor * refactor state --------- Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com>
This commit is contained in:
parent
1a6679a201
commit
35b8893b93
|
|
@ -9,29 +9,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
exchangeName := "getting-started-exchange"
|
exchangeName := "getting-started-exchange"
|
||||||
queueName := "getting-started-queue"
|
queueName := "getting-started-queue"
|
||||||
routingKey := "routing-key"
|
routingKey := "routing-key"
|
||||||
|
|
||||||
fmt.Printf("Getting started with AMQP Go AMQP 1.0 Client\n")
|
rabbitmq_amqp.Info("Getting started with AMQP Go AMQP 1.0 Client")
|
||||||
|
|
||||||
/// Create a channel to receive status change notifications
|
/// Create a channel to receive state change notifications
|
||||||
chStatusChanged := make(chan *rabbitmq_amqp.StatusChanged, 1)
|
stateChanged := make(chan *rabbitmq_amqp.StateChanged, 1)
|
||||||
go func(ch chan *rabbitmq_amqp.StatusChanged) {
|
go func(ch chan *rabbitmq_amqp.StateChanged) {
|
||||||
for statusChanged := range ch {
|
for statusChanged := range ch {
|
||||||
fmt.Printf("%s\n", statusChanged)
|
rabbitmq_amqp.Info("[Connection]", "Status changed", statusChanged)
|
||||||
}
|
}
|
||||||
}(chStatusChanged)
|
}(stateChanged)
|
||||||
|
|
||||||
// Open a connection to the AMQP 1.0 server
|
// Open a connection to the AMQP 1.0 server
|
||||||
amqpConnection, err := rabbitmq_amqp.Dial(context.Background(), "amqp://", nil)
|
amqpConnection, err := rabbitmq_amqp.Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error opening connection: %v\n", err)
|
rabbitmq_amqp.Error("Error opening connection", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Register the channel to receive status change notifications
|
// Register the channel to receive status change notifications
|
||||||
amqpConnection.NotifyStatusChange(chStatusChanged)
|
amqpConnection.NotifyStatusChange(stateChanged)
|
||||||
|
|
||||||
fmt.Printf("AMQP Connection opened.\n")
|
fmt.Printf("AMQP Connection opened.\n")
|
||||||
// Create the management interface for the connection
|
// Create the management interface for the connection
|
||||||
|
|
@ -41,7 +40,7 @@ func main() {
|
||||||
Name: exchangeName,
|
Name: exchangeName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error declaring exchange: %v\n", err)
|
rabbitmq_amqp.Error("Error declaring exchange", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +51,7 @@ func main() {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error declaring queue: %v\n", err)
|
rabbitmq_amqp.Error("Error declaring queue", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +63,7 @@ func main() {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error binding: %v\n", err)
|
rabbitmq_amqp.Error("Error binding", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,16 +71,31 @@ func main() {
|
||||||
|
|
||||||
publisher, err := amqpConnection.Publisher(context.Background(), addr, "getting-started-publisher")
|
publisher, err := amqpConnection.Publisher(context.Background(), addr, "getting-started-publisher")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error creating publisher: %v\n", err)
|
rabbitmq_amqp.Error("Error creating publisher", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish a message to the exchange
|
// Publish a message to the exchange
|
||||||
err = publisher.Publish(context.Background(), amqp.NewMessage([]byte("Hello, World!")))
|
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("Hello, World!")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error publishing message: %v\n", err)
|
rabbitmq_amqp.Error("Error publishing message", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
switch publishResult.Outcome {
|
||||||
|
case &amqp.StateAccepted{}:
|
||||||
|
rabbitmq_amqp.Info("Message accepted")
|
||||||
|
case &amqp.StateReleased{}:
|
||||||
|
rabbitmq_amqp.Warn("Message was not routed")
|
||||||
|
case &amqp.StateRejected{}:
|
||||||
|
rabbitmq_amqp.Warn("Message rejected")
|
||||||
|
stateType := publishResult.Outcome.(*amqp.StateRejected)
|
||||||
|
if stateType.Error != nil {
|
||||||
|
rabbitmq_amqp.Warn("Message rejected with error: %v", stateType.Error)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// these status are not supported
|
||||||
|
rabbitmq_amqp.Warn("Message state: %v", publishResult.Outcome)
|
||||||
|
}
|
||||||
|
|
||||||
println("press any key to close the connection")
|
println("press any key to close the connection")
|
||||||
|
|
||||||
|
|
@ -132,5 +146,5 @@ func main() {
|
||||||
// Wait for the status change to be printed
|
// Wait for the status change to be printed
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
close(chStatusChanged)
|
close(stateChangeds)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -3,7 +3,7 @@ module github.com/rabbitmq/rabbitmq-amqp-go-client
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-amqp v1.2.0
|
github.com/Azure/go-amqp v1.4.0-beta.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2
|
github.com/onsi/ginkgo/v2 v2.20.2
|
||||||
github.com/onsi/gomega v1.34.2
|
github.com/onsi/gomega v1.34.2
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -1,7 +1,5 @@
|
||||||
github.com/Azure/go-amqp v1.1.1-0.20240913224415-f631e6909719 h1:rL7yrEV9yputQV7T+Y9eJVmTVkK4B0aHlBc8TUITC5A=
|
github.com/Azure/go-amqp v1.4.0-beta.1 h1:BjZM/308FpfsQjX0gXtYK8Vx+WgQ1eng3oVQDEeXMmA=
|
||||||
github.com/Azure/go-amqp v1.1.1-0.20240913224415-f631e6909719/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
|
github.com/Azure/go-amqp v1.4.0-beta.1/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
|
||||||
github.com/Azure/go-amqp v1.2.0 h1:NNyfN3/cRszWzMvjmm64yaPZDHX/2DJkowv8Ub9y01I=
|
|
||||||
github.com/Azure/go-amqp v1.2.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package rabbitmq_amqp
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -77,16 +76,16 @@ func encodePathSegments(input string) string {
|
||||||
return encoded.String()
|
return encoded.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode takes a percent-encoded string and returns its decoded representation.
|
//// Decode takes a percent-encoded string and returns its decoded representation.
|
||||||
func decode(input string) (string, error) {
|
//func decode(input string) (string, error) {
|
||||||
// Use url.QueryUnescape which properly decodes percent-encoded strings
|
// // Use url.QueryUnescape which properly decodes percent-encoded strings
|
||||||
decoded, err := url.QueryUnescape(input)
|
// decoded, err := url.QueryUnescape(input)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return decoded, nil
|
// return decoded, nil
|
||||||
}
|
//}
|
||||||
|
|
||||||
// isUnreserved checks if a character is an unreserved character in percent encoding
|
// isUnreserved checks if a character is an unreserved character in percent encoding
|
||||||
// Unreserved characters are: A-Z, a-z, 0-9, -, ., _, ~
|
// Unreserved characters are: A-Z, a-z, 0-9, -, ., _, ~
|
||||||
|
|
@ -111,5 +110,8 @@ func bindingPathWithExchangeQueueKey(toQueue bool, sourceName, destinationName,
|
||||||
}
|
}
|
||||||
format := "/%s/src=%s;%s=%s;key=%s;args="
|
format := "/%s/src=%s;%s=%s;key=%s;args="
|
||||||
return fmt.Sprintf(format, bindings, sourceNameEncoded, destinationType, destinationNameEncoded, keyEncoded)
|
return fmt.Sprintf(format, bindings, sourceNameEncoded, destinationType, destinationNameEncoded, keyEncoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAddress(address string) bool {
|
||||||
|
return strings.HasPrefix(address, fmt.Sprintf("/%s/", exchanges)) || strings.HasPrefix(address, fmt.Sprintf("/%s/", queues))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("AMQP Bindings test ", func() {
|
var _ = Describe("AMQP Bindings test ", func() {
|
||||||
var connection IConnection
|
var connection *AmqpConnection
|
||||||
var management IManagement
|
var management *AmqpManagement
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
conn, err := Dial(context.TODO(), "amqp://", nil)
|
conn, err := Dial(context.TODO(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
connection = conn
|
connection = conn
|
||||||
management = connection.Management()
|
management = connection.Management()
|
||||||
|
|
|
||||||
|
|
@ -17,38 +17,50 @@ import (
|
||||||
|
|
||||||
type AmqpConnection struct {
|
type AmqpConnection struct {
|
||||||
Connection *amqp.Conn
|
Connection *amqp.Conn
|
||||||
management IManagement
|
management *AmqpManagement
|
||||||
lifeCycle *LifeCycle
|
lifeCycle *LifeCycle
|
||||||
session *amqp.Session
|
session *amqp.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) Publisher(ctx context.Context, destinationAdd string, linkName string) (IPublisher, error) {
|
func (a *AmqpConnection) Publisher(ctx context.Context, destinationAdd string, linkName string) (*Publisher, error) {
|
||||||
sender, err := a.session.NewSender(ctx, destinationAdd, createSenderLinkOptions(destinationAdd, linkName))
|
if !validateAddress(destinationAdd) {
|
||||||
|
return nil, fmt.Errorf("invalid destination address, the address should start with /%s/ or/%s/ ", exchanges, queues)
|
||||||
|
}
|
||||||
|
|
||||||
|
sender, err := a.session.NewSender(ctx, destinationAdd, createSenderLinkOptions(destinationAdd, linkName, AtLeastOnce))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newPublisher(sender), nil
|
return newPublisher(sender), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Management returns the management interface for the connection.
|
// Dial connect to the AMQP 1.0 server using the provided connectionSettings
|
||||||
// See IManagement interface.
|
// Returns a pointer to the new AmqpConnection if successful else an error.
|
||||||
func (a *AmqpConnection) Management() IManagement {
|
// addresses is a list of addresses to connect to. It picks one randomly.
|
||||||
return a.management
|
// It is enough that one of the addresses is reachable.
|
||||||
}
|
func Dial(ctx context.Context, addresses []string, connOptions *amqp.ConnOptions) (*AmqpConnection, error) {
|
||||||
|
|
||||||
// Dial creates a new AmqpConnection
|
|
||||||
// with a new AmqpManagement and a new LifeCycle.
|
|
||||||
// Returns a pointer to the new AmqpConnection
|
|
||||||
func Dial(ctx context.Context, addr string, connOptions *amqp.ConnOptions) (IConnection, error) {
|
|
||||||
conn := &AmqpConnection{
|
conn := &AmqpConnection{
|
||||||
management: NewAmqpManagement(),
|
management: NewAmqpManagement(),
|
||||||
lifeCycle: NewLifeCycle(),
|
lifeCycle: NewLifeCycle(),
|
||||||
}
|
}
|
||||||
err := conn.open(ctx, addr, connOptions)
|
tmp := make([]string, len(addresses))
|
||||||
if err != nil {
|
copy(tmp, addresses)
|
||||||
return nil, err
|
|
||||||
|
// random pick and extract one address to use for connection
|
||||||
|
for len(tmp) > 0 {
|
||||||
|
idx := random(len(tmp))
|
||||||
|
addr := tmp[idx]
|
||||||
|
// remove the index from the tmp list
|
||||||
|
tmp = append(tmp[:idx], tmp[idx+1:]...)
|
||||||
|
err := conn.open(ctx, addr, connOptions)
|
||||||
|
if err != nil {
|
||||||
|
Error("Failed to open connection", ExtractWithoutPassword(addr), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Debug("Connected to", ExtractWithoutPassword(addr))
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
return conn, nil
|
return nil, fmt.Errorf("no address to connect to")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a connection to the AMQP 1.0 server.
|
// Open opens a connection to the AMQP 1.0 server.
|
||||||
|
|
@ -84,30 +96,42 @@ func (a *AmqpConnection) open(ctx context.Context, addr string, connOptions *amq
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
a.lifeCycle.SetStatus(Open)
|
a.lifeCycle.SetState(&StateOpen{})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) Close(ctx context.Context) error {
|
func (a *AmqpConnection) Close(ctx context.Context) error {
|
||||||
err := a.Management().Close(ctx)
|
err := a.management.Close(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = a.Connection.Close()
|
err = a.Connection.Close()
|
||||||
a.lifeCycle.SetStatus(Closed)
|
a.lifeCycle.SetState(&StateClosed{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) NotifyStatusChange(channel chan *StatusChanged) {
|
// NotifyStatusChange registers a channel to receive getState change notifications
|
||||||
|
// from the connection.
|
||||||
|
func (a *AmqpConnection) NotifyStatusChange(channel chan *StateChanged) {
|
||||||
a.lifeCycle.chStatusChanged = channel
|
a.lifeCycle.chStatusChanged = channel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) Status() int {
|
func (a *AmqpConnection) State() LifeCycleState {
|
||||||
return a.lifeCycle.Status()
|
return a.lifeCycle.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *** management section ***
|
||||||
|
|
||||||
|
// Management returns the management interface for the connection.
|
||||||
|
// The management interface is used to declare and delete exchanges, queues, and bindings.
|
||||||
|
func (a *AmqpConnection) Management() *AmqpManagement {
|
||||||
|
return a.management
|
||||||
|
}
|
||||||
|
|
||||||
|
//*** end management section ***
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
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://", &amqp.ConnOptions{
|
connection, err := Dial(context.Background(), []string{"amqp://"}, &amqp.ConnOptions{
|
||||||
SASLType: amqp.SASLTypeAnonymous()})
|
SASLType: amqp.SASLTypeAnonymous()})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
err = connection.Close(context.Background())
|
err = connection.Close(context.Background())
|
||||||
|
|
@ -20,7 +20,7 @@ var _ = Describe("AMQP Connection Test", func() {
|
||||||
|
|
||||||
It("AMQP SASLTypePlain Connection should succeed", func() {
|
It("AMQP SASLTypePlain Connection should succeed", func() {
|
||||||
|
|
||||||
connection, err := Dial(context.Background(), "amqp://", &amqp.ConnOptions{
|
connection, err := Dial(context.Background(), []string{"amqp://"}, &amqp.ConnOptions{
|
||||||
SASLType: amqp.SASLTypePlain("guest", "guest")})
|
SASLType: amqp.SASLTypePlain("guest", "guest")})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -28,26 +28,37 @@ var _ = Describe("AMQP Connection Test", func() {
|
||||||
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() {
|
It("AMQP Connection should fail due of wrong Port", func() {
|
||||||
_, err := Dial(context.Background(), "amqp://localhost:1234", nil)
|
_, err := Dial(context.Background(), []string{"amqp://localhost:1234"}, nil)
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP Connection should fail due of wrong Host", func() {
|
It("AMQP Connection should fail due of wrong Host", func() {
|
||||||
_, err := Dial(context.Background(), "amqp://wrong_host:5672", nil)
|
_, 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())
|
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, "amqp://", nil)
|
_, err := Dial(ctx, []string{"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 *StatusChanged, 1)
|
ch := make(chan *StateChanged, 1)
|
||||||
connection, err := Dial(context.Background(), "amqp://", nil)
|
connection, err := Dial(context.Background(), []string{"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())
|
||||||
|
|
@ -55,8 +66,8 @@ var _ = Describe("AMQP Connection Test", func() {
|
||||||
|
|
||||||
recv := <-ch
|
recv := <-ch
|
||||||
Expect(recv).NotTo(BeNil())
|
Expect(recv).NotTo(BeNil())
|
||||||
Expect(recv.From).To(Equal(Open))
|
Expect(recv.From).To(Equal(&StateOpen{}))
|
||||||
Expect(recv.To).To(Equal(Closed))
|
Expect(recv.To).To(Equal(&StateClosed{}))
|
||||||
})
|
})
|
||||||
|
|
||||||
//It("AMQP TLS Connection should success with SASLTypeAnonymous ", func() {
|
//It("AMQP TLS Connection should success with SASLTypeAnonymous ", func() {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ type AmqpExchangeInfo struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAmqpExchangeInfo(name string) IExchangeInfo {
|
func newAmqpExchangeInfo(name string) *AmqpExchangeInfo {
|
||||||
return &AmqpExchangeInfo{name: name}
|
return &AmqpExchangeInfo{name: name}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ func newAmqpExchange(management *AmqpManagement, name string) *AmqpExchange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AmqpExchange) Declare(ctx context.Context) (IExchangeInfo, error) {
|
func (e *AmqpExchange) Declare(ctx context.Context) (*AmqpExchangeInfo, error) {
|
||||||
path, err := ExchangeAddress(&e.name, nil)
|
path, err := ExchangeAddress(&e.name, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("AMQP Exchange test ", func() {
|
var _ = Describe("AMQP Exchange test ", func() {
|
||||||
var connection IConnection
|
var connection *AmqpConnection
|
||||||
var management IManagement
|
var management *AmqpManagement
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
conn, err := Dial(context.TODO(), "amqp://", nil)
|
conn, err := Dial(context.TODO(), []string{"amqp://"}, nil)
|
||||||
connection = conn
|
connection = conn
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
management = connection.Management()
|
management = connection.Management()
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func (a *AmqpManagement) ensureReceiverLink(ctx context.Context) error {
|
||||||
func (a *AmqpManagement) ensureSenderLink(ctx context.Context) error {
|
func (a *AmqpManagement) ensureSenderLink(ctx context.Context) error {
|
||||||
if a.sender == nil {
|
if a.sender == nil {
|
||||||
sender, err := a.session.NewSender(ctx, managementNodeAddress,
|
sender, err := a.session.NewSender(ctx, managementNodeAddress,
|
||||||
createSenderLinkOptions(managementNodeAddress, linkPairName))
|
createSenderLinkOptions(managementNodeAddress, linkPairName, AtMostOnce))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -54,8 +54,8 @@ func (a *AmqpManagement) ensureSenderLink(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) Open(ctx context.Context, connection IConnection) error {
|
func (a *AmqpManagement) Open(ctx context.Context, connection *AmqpConnection) error {
|
||||||
session, err := connection.(*AmqpConnection).Connection.NewSession(ctx, nil)
|
session, err := connection.Connection.NewSession(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ func (a *AmqpManagement) Open(ctx context.Context, connection IConnection) error
|
||||||
// some channels or I/O or something elsewhere
|
// some channels or I/O or something elsewhere
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
|
||||||
a.lifeCycle.SetStatus(Open)
|
a.lifeCycle.SetState(&StateOpen{})
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ func (a *AmqpManagement) Close(ctx context.Context) error {
|
||||||
_ = a.sender.Close(ctx)
|
_ = a.sender.Close(ctx)
|
||||||
_ = a.receiver.Close(ctx)
|
_ = a.receiver.Close(ctx)
|
||||||
err := a.session.Close(ctx)
|
err := a.session.Close(ctx)
|
||||||
a.lifeCycle.SetStatus(Closed)
|
a.lifeCycle.SetState(&StateClosed{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,7 +170,7 @@ 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) (IQueueInfo, error) {
|
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification *QueueSpecification) (*AmqpQueueInfo, error) {
|
||||||
var amqpQueue *AmqpQueue
|
var amqpQueue *AmqpQueue
|
||||||
|
|
||||||
if specification == nil || len(specification.Name) <= 0 {
|
if specification == nil || len(specification.Name) <= 0 {
|
||||||
|
|
@ -195,7 +195,7 @@ 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) (IExchangeInfo, error) {
|
func (a *AmqpManagement) DeclareExchange(ctx context.Context, exchangeSpecification *ExchangeSpecification) (*AmqpExchangeInfo, error) {
|
||||||
if exchangeSpecification == nil {
|
if exchangeSpecification == nil {
|
||||||
return nil, fmt.Errorf("exchangeSpecification is nil")
|
return nil, fmt.Errorf("exchangeSpecification is nil")
|
||||||
}
|
}
|
||||||
|
|
@ -224,7 +224,7 @@ func (a *AmqpManagement) Unbind(ctx context.Context, bindingPath string) error {
|
||||||
bind := newAMQPBinding(a)
|
bind := newAMQPBinding(a)
|
||||||
return bind.Unbind(ctx, bindingPath)
|
return bind.Unbind(ctx, bindingPath)
|
||||||
}
|
}
|
||||||
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (IQueueInfo, error) {
|
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*AmqpQueueInfo, error) {
|
||||||
path, err := QueueAddress(&queueName)
|
path, err := QueueAddress(&queueName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -241,10 +241,10 @@ func (a *AmqpManagement) PurgeQueue(ctx context.Context, queueName string) (int,
|
||||||
return purge.Purge(ctx)
|
return purge.Purge(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) NotifyStatusChange(channel chan *StatusChanged) {
|
func (a *AmqpManagement) NotifyStatusChange(channel chan *StateChanged) {
|
||||||
a.lifeCycle.chStatusChanged = channel
|
a.lifeCycle.chStatusChanged = channel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) Status() int {
|
func (a *AmqpManagement) State() LifeCycleState {
|
||||||
return a.lifeCycle.Status()
|
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(), "amqp://", nil)
|
connection, err := Dial(context.Background(), []string{"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)
|
||||||
|
|
@ -22,8 +22,8 @@ var _ = Describe("Management tests", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP Management should receive events", func() {
|
It("AMQP Management should receive events", func() {
|
||||||
ch := make(chan *StatusChanged, 1)
|
ch := make(chan *StateChanged, 1)
|
||||||
connection, err := Dial(context.Background(), "amqp://", nil)
|
connection, err := Dial(context.Background(), []string{"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())
|
||||||
|
|
@ -31,14 +31,14 @@ var _ = Describe("Management tests", func() {
|
||||||
recv := <-ch
|
recv := <-ch
|
||||||
Expect(recv).NotTo(BeNil())
|
Expect(recv).NotTo(BeNil())
|
||||||
|
|
||||||
Expect(recv.From).To(Equal(Open))
|
Expect(recv.From).To(Equal(&StateOpen{}))
|
||||||
Expect(recv.To).To(Equal(Closed))
|
Expect(recv.To).To(Equal(&StateClosed{}))
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Request", func() {
|
It("Request", func() {
|
||||||
|
|
||||||
connection, err := Dial(context.Background(), "amqp://", nil)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
management := connection.Management()
|
management := connection.Management()
|
||||||
|
|
@ -58,7 +58,7 @@ var _ = Describe("Management tests", func() {
|
||||||
|
|
||||||
It("GET on non-existing queue returns ErrDoesNotExist", func() {
|
It("GET on non-existing queue returns ErrDoesNotExist", func() {
|
||||||
|
|
||||||
connection, err := Dial(context.Background(), "amqp://", nil)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
management := connection.Management()
|
management := connection.Management()
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ import (
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PublishResult struct {
|
||||||
|
Outcome amqp.DeliveryState
|
||||||
|
Message *amqp.Message
|
||||||
|
}
|
||||||
|
|
||||||
type Publisher struct {
|
type Publisher struct {
|
||||||
sender *amqp.Sender
|
sender *amqp.Sender
|
||||||
}
|
}
|
||||||
|
|
@ -13,26 +18,33 @@ func newPublisher(sender *amqp.Sender) *Publisher {
|
||||||
return &Publisher{sender: sender}
|
return &Publisher{sender: sender}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Publisher) Publish(ctx context.Context, message *amqp.Message) error {
|
// Publish sends a message to the destination address.
|
||||||
|
// The message is sent to the destination address and the outcome of the operation is returned.
|
||||||
/// for the outcome of the message delivery, see https://github.com/Azure/go-amqp/issues/347
|
// The outcome is a DeliveryState that indicates if the message was accepted or rejected.
|
||||||
//RELEASED
|
// RabbitMQ supports the following DeliveryState types:
|
||||||
///**
|
// - StateAccepted
|
||||||
// * The broker could not route the message to any queue.
|
// - StateReleased
|
||||||
// *
|
// - StateRejected
|
||||||
// * <p>This is likely to be due to a topology misconfiguration.
|
// See: https://www.rabbitmq.com/docs/next/amqp#outcomes for more information.
|
||||||
// */
|
func (m *Publisher) Publish(ctx context.Context, message *amqp.Message) (*PublishResult, error) {
|
||||||
// so at the moment we don't have access on this information
|
|
||||||
// TODO: remove this comment when the issue is resolved
|
|
||||||
|
|
||||||
err := m.sender.Send(ctx, message, nil)
|
|
||||||
|
|
||||||
|
r, err := m.sender.SendWithReceipt(ctx, message, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil
|
state, err := r.Wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
publishResult := &PublishResult{
|
||||||
|
Message: message,
|
||||||
|
Outcome: state,
|
||||||
|
}
|
||||||
|
return publishResult, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes the publisher.
|
||||||
func (m *Publisher) Close(ctx context.Context) error {
|
func (m *Publisher) Close(ctx context.Context) error {
|
||||||
return m.sender.Close(ctx)
|
return m.sender.Close(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 Publisher", func() {
|
It("Send a message to a queue with a Message Target Publisher", func() {
|
||||||
qName := generateNameWithDateTime("Send a message to a queue with a Message Target Publisher")
|
qName := generateNameWithDateTime("Send a message to a queue with a Message Target Publisher")
|
||||||
connection, err := Dial(context.Background(), "amqp://", nil)
|
connection, err := Dial(context.Background(), []string{"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(), &QueueSpecification{
|
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||||
|
|
@ -24,13 +24,90 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
Expect(publisher).To(BeAssignableToTypeOf(&Publisher{}))
|
Expect(publisher).To(BeAssignableToTypeOf(&Publisher{}))
|
||||||
|
|
||||||
err = publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publishResult).NotTo(BeNil())
|
||||||
|
Expect(publishResult.Outcome).To(Equal(&amqp.StateAccepted{}))
|
||||||
|
|
||||||
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
nMessages, err := connection.Management().PurgeQueue(context.Background(), qName)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(nMessages).To(Equal(1))
|
Expect(nMessages).To(Equal(1))
|
||||||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
|
||||||
Expect(publisher.Close(context.Background())).To(BeNil())
|
Expect(publisher.Close(context.Background())).To(BeNil())
|
||||||
|
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Publisher should fail to a not existing exchange", func() {
|
||||||
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
exchangeName := "Nope"
|
||||||
|
addr, err := ExchangeAddress(&exchangeName, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
publisher, err := connection.Publisher(context.Background(), addr, "test")
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
Expect(publisher).To(BeNil())
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Publisher should fail if the destination address does not start in the correct way", func() {
|
||||||
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
destinationAddress := "this is not valid since does not start with exchanges or queues"
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
publisher, err := connection.Publisher(context.Background(), destinationAddress, "test")
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
Expect(publisher).To(BeNil())
|
||||||
|
Expect(err.Error()).To(ContainSubstring("invalid destination address"))
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("publishResult should released to a not existing routing key", func() {
|
||||||
|
eName := generateNameWithDateTime("publishResult should released to a not existing routing key")
|
||||||
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
exchange, err := connection.Management().DeclareExchange(context.Background(), &ExchangeSpecification{
|
||||||
|
Name: eName,
|
||||||
|
IsAutoDelete: false,
|
||||||
|
ExchangeType: ExchangeType{Type: Topic},
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(exchange).NotTo(BeNil())
|
||||||
|
routingKeyNope := "I don't exist"
|
||||||
|
addr, err := ExchangeAddress(&eName, &routingKeyNope)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
publisher, err := connection.Publisher(context.Background(), addr, "test")
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publishResult).NotTo(BeNil())
|
||||||
|
Expect(publishResult.Outcome).To(Equal(&amqp.StateReleased{}))
|
||||||
|
Expect(connection.Management().DeleteExchange(context.Background(), eName)).To(BeNil())
|
||||||
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Send a message to a deleted queue should fail", func() {
|
||||||
|
qName := generateNameWithDateTime("Send a message to a deleted queue should fail")
|
||||||
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(connection).NotTo(BeNil())
|
||||||
|
_, err = connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||||
|
Name: qName,
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
dest, _ := QueueAddress(&qName)
|
||||||
|
publisher, err := connection.Publisher(context.Background(), dest, "test")
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(publishResult.Outcome).To(Equal(&amqp.StateAccepted{}))
|
||||||
|
err = connection.management.DeleteQueue(context.Background(), qName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
publishResult, err = publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
||||||
|
Expect(err).NotTo(BeNil())
|
||||||
|
Expect(connection.Close(context.Background()))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func (a *AmqpQueueInfo) Members() []string {
|
||||||
return a.members
|
return a.members
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAmqpQueueInfo(response map[string]any) IQueueInfo {
|
func newAmqpQueueInfo(response map[string]any) *AmqpQueueInfo {
|
||||||
return &AmqpQueueInfo{
|
return &AmqpQueueInfo{
|
||||||
name: response["name"].(string),
|
name: response["name"].(string),
|
||||||
isDurable: response["durable"].(bool),
|
isDurable: response["durable"].(bool),
|
||||||
|
|
@ -133,7 +133,7 @@ func (a *AmqpQueue) validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpQueue) Declare(ctx context.Context) (IQueueInfo, error) {
|
func (a *AmqpQueue) Declare(ctx context.Context) (*AmqpQueueInfo, error) {
|
||||||
if Quorum == a.GetQueueType() ||
|
if Quorum == a.GetQueueType() ||
|
||||||
Stream == a.GetQueueType() {
|
Stream == a.GetQueueType() {
|
||||||
// mandatory arguments for quorum queues and streams
|
// mandatory arguments for quorum queues and streams
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("AMQP Queue test ", func() {
|
var _ = Describe("AMQP Queue test ", func() {
|
||||||
var connection IConnection
|
var connection *AmqpConnection
|
||||||
var management IManagement
|
var management *AmqpManagement
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
conn, err := Dial(context.TODO(), "amqp://", nil)
|
conn, err := Dial(context.TODO(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
connection = conn
|
connection = conn
|
||||||
management = connection.Management()
|
management = connection.Management()
|
||||||
|
|
@ -148,7 +148,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
It("AMQP Declare Queue should fail with Precondition fail", func() {
|
It("AMQP Declare Queue should fail with Precondition fail", func() {
|
||||||
// The first queue is declared as Classic, and it should succeed
|
// The first queue is declared as Classic, and it should succeed
|
||||||
// The second queue is declared as Quorum, and it should fail since it is already declared as Classic
|
// The second queue is declared as Quorum, and it should fail since it is already declared as Classic
|
||||||
const queueName = "AMQP Declare Queue should fail with Precondition fail"
|
queueName := generateName("AMQP Declare Queue should fail with Precondition fail")
|
||||||
|
|
||||||
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
|
|
@ -168,7 +168,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP Declare Queue should fail during validation", func() {
|
It("AMQP Declare Queue should fail during validation", func() {
|
||||||
const queueName = "AMQP Declare Queue should fail during validation"
|
queueName := generateName("AMQP Declare Queue should fail during validation")
|
||||||
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
MaxLengthBytes: -1,
|
MaxLengthBytes: -1,
|
||||||
|
|
@ -188,7 +188,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP Purge Queue should succeed and return the number of messages purged", func() {
|
It("AMQP Purge Queue should succeed and return the number of messages purged", func() {
|
||||||
const queueName = "AMQP Purge Queue should succeed and return the number of messages purged"
|
queueName := generateName("AMQP Purge Queue should succeed and return the number of messages purged")
|
||||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
})
|
})
|
||||||
|
|
@ -197,6 +197,8 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
purged, err := management.PurgeQueue(context.TODO(), queueInfo.Name())
|
purged, err := management.PurgeQueue(context.TODO(), queueInfo.Name())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(purged).To(Equal(10))
|
Expect(purged).To(Equal(10))
|
||||||
|
err = management.DeleteQueue(context.TODO(), queueName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP GET on non-existing queue should return ErrDoesNotExist", func() {
|
It("AMQP GET on non-existing queue should return ErrDoesNotExist", func() {
|
||||||
|
|
@ -207,33 +209,24 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: This should be replaced with this library's publish function
|
|
||||||
// but for the time being, we need a way to publish messages or test purposes
|
|
||||||
func publishMessages(queueName string, count int) {
|
func publishMessages(queueName string, count int) {
|
||||||
conn, err := amqp.Dial(context.TODO(), "amqp://guest:guest@localhost", nil)
|
conn, err := Dial(context.TODO(), []string{"amqp://guest:guest@localhost"}, nil)
|
||||||
if err != nil {
|
Expect(err).To(BeNil())
|
||||||
Fail(err.Error())
|
|
||||||
}
|
|
||||||
session, err := conn.NewSession(context.TODO(), nil)
|
|
||||||
if err != nil {
|
|
||||||
Fail(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
address, err := QueueAddress(&queueName)
|
address, err := QueueAddress(&queueName)
|
||||||
if err != nil {
|
Expect(err).To(BeNil())
|
||||||
Fail(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
sender, err := session.NewSender(context.TODO(), address, nil)
|
publisher, err := conn.Publisher(context.TODO(), address, "test")
|
||||||
if err != nil {
|
Expect(err).To(BeNil())
|
||||||
Fail(err.Error())
|
Expect(publisher).NotTo(BeNil())
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
err = sender.Send(context.TODO(), amqp.NewMessage([]byte("Message #"+strconv.Itoa(i))), nil)
|
publishResult, err := publisher.Publish(context.TODO(), amqp.NewMessage([]byte("Message #"+strconv.Itoa(i))))
|
||||||
if err != nil {
|
Expect(err).To(BeNil())
|
||||||
Fail(err.Error())
|
Expect(publishResult).NotTo(BeNil())
|
||||||
}
|
Expect(publishResult.Outcome).To(Equal(&amqp.StateAccepted{}))
|
||||||
}
|
}
|
||||||
|
err = conn.Close(context.TODO())
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,29 @@
|
||||||
package rabbitmq_amqp
|
package rabbitmq_amqp
|
||||||
|
|
||||||
import "github.com/Azure/go-amqp"
|
import (
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const AtMostOnce = 0
|
||||||
|
const AtLeastOnce = 1
|
||||||
|
|
||||||
// senderLinkOptions returns the options for a sender link
|
// senderLinkOptions returns the options for a sender 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 createSenderLinkOptions(address string, linkName string) *amqp.SenderOptions {
|
func createSenderLinkOptions(address string, linkName string, deliveryMode int) *amqp.SenderOptions {
|
||||||
prop := make(map[string]any)
|
prop := make(map[string]any)
|
||||||
prop["paired"] = true
|
prop["paired"] = true
|
||||||
|
sndSettleMode := amqp.SenderSettleModeSettled.Ptr()
|
||||||
|
/// SndSettleMode = deliveryMode == DeliveryMode.AtMostOnce
|
||||||
|
// ? SenderSettleMode.Settled
|
||||||
|
// : SenderSettleMode.Unsettled,
|
||||||
|
|
||||||
|
if deliveryMode == AtLeastOnce {
|
||||||
|
sndSettleMode = amqp.SenderSettleModeUnsettled.Ptr()
|
||||||
|
}
|
||||||
|
|
||||||
return &amqp.SenderOptions{
|
return &amqp.SenderOptions{
|
||||||
SourceAddress: address,
|
SourceAddress: address,
|
||||||
DynamicAddress: false,
|
DynamicAddress: false,
|
||||||
|
|
@ -15,7 +31,7 @@ func createSenderLinkOptions(address string, linkName string) *amqp.SenderOption
|
||||||
ExpiryTimeout: 0,
|
ExpiryTimeout: 0,
|
||||||
Name: linkName,
|
Name: linkName,
|
||||||
Properties: prop,
|
Properties: prop,
|
||||||
SettlementMode: amqp.SenderSettleModeSettled.Ptr(),
|
SettlementMode: sndSettleMode,
|
||||||
RequestedReceiverSettleMode: amqp.ReceiverSettleModeFirst.Ptr(),
|
RequestedReceiverSettleMode: amqp.ReceiverSettleModeFirst.Ptr(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,3 +53,8 @@ func createReceiverLinkOptions(address string, linkName string) *amqp.ReceiverOp
|
||||||
Credit: 100,
|
Credit: 100,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func random(max int) int {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().Unix()))
|
||||||
|
return r.Intn(max)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package rabbitmq_amqp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/Azure/go-amqp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IConnection interface {
|
|
||||||
|
|
||||||
// Close closes the connection to the AMQP 1.0 server.
|
|
||||||
Close(ctx context.Context) error
|
|
||||||
|
|
||||||
// Management returns the management interface for the connection.
|
|
||||||
Management() IManagement
|
|
||||||
|
|
||||||
// NotifyStatusChange registers a channel to receive status change notifications.
|
|
||||||
// The channel will receive a StatusChanged struct whenever the status of the connection changes.
|
|
||||||
NotifyStatusChange(channel chan *StatusChanged)
|
|
||||||
// Status returns the current status of the connection.
|
|
||||||
// See LifeCycle struct for more information.
|
|
||||||
Status() int
|
|
||||||
|
|
||||||
// Publisher returns a new IPublisher interface for the connection.
|
|
||||||
Publisher(ctx context.Context, destinationAddr string, linkName string) (IPublisher, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPublisher is an interface for publishers messages based.
|
|
||||||
// on the AMQP 1.0 protocol.
|
|
||||||
type IPublisher interface {
|
|
||||||
Publish(ctx context.Context, message *amqp.Message) error
|
|
||||||
Close(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
@ -27,20 +27,6 @@ type QueueSpecification struct {
|
||||||
DeadLetterRoutingKey string
|
DeadLetterRoutingKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IQueueInfo represents the information of a queue
|
|
||||||
// It is returned by the Declare method of IQueueSpecification
|
|
||||||
// The information come from the server
|
|
||||||
type IQueueInfo interface {
|
|
||||||
Name() string
|
|
||||||
IsDurable() bool
|
|
||||||
IsAutoDelete() bool
|
|
||||||
IsExclusive() bool
|
|
||||||
Type() TQueueType
|
|
||||||
Leader() string
|
|
||||||
Members() []string
|
|
||||||
Arguments() map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
type TExchangeType string
|
type TExchangeType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -57,13 +43,6 @@ func (e ExchangeType) String() string {
|
||||||
return string(e.Type)
|
return string(e.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IExchangeInfo represents the information of an exchange
|
|
||||||
// It is empty at the moment because the server does not return any information
|
|
||||||
// We leave it here for future use. In case the server returns information about an exchange
|
|
||||||
type IExchangeInfo interface {
|
|
||||||
Name() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExchangeSpecification struct {
|
type ExchangeSpecification struct {
|
||||||
Name string
|
Name string
|
||||||
IsAutoDelete bool
|
IsAutoDelete bool
|
||||||
|
|
|
||||||
|
|
@ -5,70 +5,102 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LifeCycleState interface {
|
||||||
|
getState() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateOpen struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *StateOpen) getState() int {
|
||||||
|
return open
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateReconnecting struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StateReconnecting) getState() int {
|
||||||
|
return reconnecting
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateClosing struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StateClosing) getState() int {
|
||||||
|
return closing
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateClosed struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StateClosed) getState() int {
|
||||||
|
return closed
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Open = iota
|
open = iota
|
||||||
Reconnecting = iota
|
reconnecting = iota
|
||||||
Closing = iota
|
closing = iota
|
||||||
Closed = iota
|
closed = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
func statusToString(status int) string {
|
func statusToString(status LifeCycleState) string {
|
||||||
switch status {
|
switch status.getState() {
|
||||||
case Open:
|
case open:
|
||||||
return "Open"
|
return "open"
|
||||||
case Reconnecting:
|
case reconnecting:
|
||||||
return "Reconnecting"
|
return "reconnecting"
|
||||||
case Closing:
|
case closing:
|
||||||
return "Closing"
|
return "closing"
|
||||||
case Closed:
|
case closed:
|
||||||
return "Closed"
|
return "closed"
|
||||||
}
|
}
|
||||||
return "Unknown"
|
return "unknown"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusChanged struct {
|
type StateChanged struct {
|
||||||
From int
|
From LifeCycleState
|
||||||
To int
|
To LifeCycleState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s StatusChanged) String() string {
|
func (s StateChanged) String() string {
|
||||||
return fmt.Sprintf("From: %s, To: %s", statusToString(s.From), statusToString(s.To))
|
return fmt.Sprintf("From: %s, To: %s", statusToString(s.From), statusToString(s.To))
|
||||||
}
|
}
|
||||||
|
|
||||||
type LifeCycle struct {
|
type LifeCycle struct {
|
||||||
status int
|
state LifeCycleState
|
||||||
chStatusChanged chan *StatusChanged
|
chStatusChanged chan *StateChanged
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifeCycle() *LifeCycle {
|
func NewLifeCycle() *LifeCycle {
|
||||||
return &LifeCycle{
|
return &LifeCycle{
|
||||||
status: Closed,
|
state: &StateClosed{},
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) Status() int {
|
func (l *LifeCycle) State() LifeCycleState {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
return l.status
|
return l.state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) SetStatus(value int) {
|
func (l *LifeCycle) SetState(value LifeCycleState) {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if l.status == value {
|
if l.state == value {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldState := l.status
|
oldState := l.state
|
||||||
l.status = value
|
l.state = value
|
||||||
|
|
||||||
if l.chStatusChanged == nil {
|
if l.chStatusChanged == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.chStatusChanged <- &StatusChanged{
|
l.chStatusChanged <- &StateChanged{
|
||||||
From: oldState,
|
From: oldState,
|
||||||
To: value,
|
To: value,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package rabbitmq_amqp
|
||||||
|
|
||||||
|
import "log/slog"
|
||||||
|
|
||||||
|
func Info(msg string, args ...any) {
|
||||||
|
slog.Info(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(msg string, args ...any) {
|
||||||
|
slog.Debug(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(msg string, args ...any) {
|
||||||
|
slog.Error(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(msg string, args ...any) {
|
||||||
|
slog.Warn(msg, args...)
|
||||||
|
}
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package rabbitmq_amqp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IManagement interface {
|
|
||||||
// Open setups the sender and receiver links to the management interface.
|
|
||||||
Open(ctx context.Context, connection IConnection) error
|
|
||||||
// Close closes the sender and receiver links to the management interface.
|
|
||||||
Close(ctx context.Context) error
|
|
||||||
|
|
||||||
// DeclareQueue creates a queue with the specified specification.
|
|
||||||
DeclareQueue(ctx context.Context, specification *QueueSpecification) (IQueueInfo, error)
|
|
||||||
// DeleteQueue deletes the queue with the specified name.
|
|
||||||
DeleteQueue(ctx context.Context, name string) error
|
|
||||||
// DeclareExchange creates an exchange with the specified specification.
|
|
||||||
DeclareExchange(ctx context.Context, exchangeSpecification *ExchangeSpecification) (IExchangeInfo, error)
|
|
||||||
// DeleteExchange deletes the exchange with the specified name.
|
|
||||||
DeleteExchange(ctx context.Context, name string) error
|
|
||||||
//Bind creates a binding between an exchange and a queue or exchange
|
|
||||||
Bind(ctx context.Context, bindingSpecification *BindingSpecification) (string, error)
|
|
||||||
// Unbind removes a binding between an exchange and a queue or exchange given the binding path.
|
|
||||||
Unbind(ctx context.Context, bindingPath string) error
|
|
||||||
// PurgeQueue removes all messages from the queue. Returns the number of messages purged.
|
|
||||||
PurgeQueue(ctx context.Context, queueName string) (int, error)
|
|
||||||
// QueueInfo returns information about the queue with the specified name.
|
|
||||||
QueueInfo(ctx context.Context, queueName string) (IQueueInfo, error)
|
|
||||||
|
|
||||||
// Status returns the current status of the management interface.
|
|
||||||
// See LifeCycle struct for more information.
|
|
||||||
Status() int
|
|
||||||
|
|
||||||
// NotifyStatusChange registers a channel to receive status change notifications.
|
|
||||||
// The channel will receive a StatusChanged struct whenever the status of the management interface changes.
|
|
||||||
NotifyStatusChange(channel chan *StatusChanged)
|
|
||||||
|
|
||||||
//Request sends a request to the management interface with the specified body, path, and method.
|
|
||||||
//Returns the response body as a map[string]any.
|
|
||||||
//It usually is not necessary to call this method directly. Leave it public for custom use cases.
|
|
||||||
// The calls above are the recommended way to interact with the management interface.
|
|
||||||
Request(ctx context.Context, body any, path string, method string,
|
|
||||||
expectedResponseCodes []int) (map[string]any, error)
|
|
||||||
}
|
|
||||||
|
|
@ -115,3 +115,13 @@ func ParseURI(uri string) (URI, error) {
|
||||||
|
|
||||||
return builder, nil
|
return builder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract the Uri by omitting the password
|
||||||
|
|
||||||
|
func ExtractWithoutPassword(addr string) string {
|
||||||
|
u, err := ParseURI(addr)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return u.Scheme + "://" + u.Username + "@*****" + u.Host + ":" + strconv.Itoa(u.Port) + u.Vhost
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue