368 строки
11 KiB
Go
368 строки
11 KiB
Go
package servicebus
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"errors"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Azure/go-autorest/autorest/to"
|
|
"github.com/devigned/tab"
|
|
|
|
"github.com/Azure/azure-service-bus-go/atom"
|
|
)
|
|
|
|
type (
|
|
// QueueManager provides CRUD functionality for Service Bus Queues
|
|
QueueManager struct {
|
|
*entityManager
|
|
}
|
|
|
|
// Entity is represents the most basic form of an Azure Service Bus entity.
|
|
Entity struct {
|
|
Name string
|
|
ID string
|
|
}
|
|
|
|
// QueueEntity is the Azure Service Bus description of a Queue for management activities
|
|
QueueEntity struct {
|
|
*QueueDescription
|
|
*Entity
|
|
}
|
|
|
|
// queueFeed is a specialized feed containing QueueEntries
|
|
queueFeed struct {
|
|
*atom.Feed
|
|
Entries []queueEntry `xml:"entry"`
|
|
}
|
|
|
|
// queueEntry is a specialized Queue feed entry
|
|
queueEntry struct {
|
|
*atom.Entry
|
|
Content *queueContent `xml:"content"`
|
|
}
|
|
|
|
// QueueManagementOption represents named configuration options for queue mutation
|
|
QueueManagementOption func(*QueueDescription) error
|
|
|
|
// Targetable provides the ability to forward messages to the entity
|
|
Targetable interface {
|
|
TargetURI() string
|
|
}
|
|
)
|
|
|
|
// TargetURI provides an absolute address to a target entity
|
|
func (e Entity) TargetURI() string {
|
|
split := strings.Split(e.ID, "?")
|
|
return split[0]
|
|
}
|
|
|
|
func queueEntryToEntity(entry *queueEntry) *QueueEntity {
|
|
return &QueueEntity{
|
|
QueueDescription: &entry.Content.QueueDescription,
|
|
Entity: &Entity{
|
|
Name: entry.Title,
|
|
ID: entry.ID,
|
|
},
|
|
}
|
|
}
|
|
|
|
/*
|
|
QueueEntityWithPartitioning ensure the created queue will be a partitioned queue. Partitioned queues offer increased
|
|
storage and availability compared to non-partitioned queues with the trade-off of requiring the following to ensure
|
|
FIFO message retrieval:
|
|
|
|
SessionId. If a message has the SessionId property set, then Service Bus uses the SessionId property as the
|
|
partition key. This way, all messages that belong to the same session are assigned to the same fragment and handled
|
|
by the same message broker. This allows Service Bus to guarantee message ordering as well as the consistency of
|
|
session states.
|
|
|
|
PartitionKey. If a message has the PartitionKey property set but not the SessionId property, then Service Bus uses
|
|
the PartitionKey property as the partition key. Use the PartitionKey property to send non-sessionful transactional
|
|
messages. The partition key ensures that all messages that are sent within a transaction are handled by the same
|
|
messaging broker.
|
|
|
|
MessageId. If the queue has the RequiresDuplicationDetection property set to true, then the MessageId
|
|
property serves as the partition key if the SessionId or a PartitionKey properties are not set. This ensures that
|
|
all copies of the same message are handled by the same message broker and, thus, allows Service Bus to detect and
|
|
eliminate duplicate messages
|
|
*/
|
|
func QueueEntityWithPartitioning() QueueManagementOption {
|
|
return func(queue *QueueDescription) error {
|
|
queue.EnablePartitioning = ptrBool(true)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithMaxSizeInMegabytes configures the maximum size of the queue in megabytes (1 * 1024 - 5 * 1024), which is the size of
|
|
// the memory allocated for the queue. Default is 1 MB (1 * 1024).
|
|
//
|
|
// size must be between 1024 and 5 * 1024 for the Standard sku and up to 80 * 1024 for Premium sku
|
|
func QueueEntityWithMaxSizeInMegabytes(size int) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
if size < 1024 || size > 80*1024 {
|
|
return errors.New("QueueEntityWithMaxSizeInMegabytes: must be between 1024 and 5 * 1024 for the Standard sku and up to 80 * 1024 for Premium sku")
|
|
}
|
|
int32Size := int32(size)
|
|
q.MaxSizeInMegabytes = &int32Size
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithDuplicateDetection configures the queue to detect duplicates for a given time window. If window
|
|
// is not specified, then it uses the default of 10 minutes.
|
|
func QueueEntityWithDuplicateDetection(window *time.Duration) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
q.RequiresDuplicateDetection = ptrBool(true)
|
|
if window != nil {
|
|
q.DuplicateDetectionHistoryTimeWindow = ptrString(durationTo8601Seconds(*window))
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithRequiredSessions will ensure the queue requires senders and receivers to have sessionIDs
|
|
func QueueEntityWithRequiredSessions() QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
q.RequiresSession = ptrBool(true)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithDeadLetteringOnMessageExpiration will ensure the queue sends expired messages to the dead letter queue
|
|
func QueueEntityWithDeadLetteringOnMessageExpiration() QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
q.DeadLetteringOnMessageExpiration = ptrBool(true)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithAutoDeleteOnIdle configures the queue to automatically delete after the specified idle interval. The
|
|
// minimum duration is 5 minutes.
|
|
func QueueEntityWithAutoDeleteOnIdle(window *time.Duration) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
if window != nil {
|
|
if window.Minutes() < 5 {
|
|
return errors.New("QueueEntityWithAutoDeleteOnIdle: window must be greater than 5 minutes")
|
|
}
|
|
q.AutoDeleteOnIdle = ptrString(durationTo8601Seconds(*window))
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithMessageTimeToLive configures the queue to set a time to live on messages. This is the duration after which
|
|
// the message expires, starting from when the message is sent to Service Bus. This is the default value used when
|
|
// TimeToLive is not set on a message itself. If nil, defaults to 14 days.
|
|
func QueueEntityWithMessageTimeToLive(window *time.Duration) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
if window == nil {
|
|
duration := time.Duration(14 * 24 * time.Hour)
|
|
window = &duration
|
|
}
|
|
q.DefaultMessageTimeToLive = ptrString(durationTo8601Seconds(*window))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithLockDuration configures the queue to have a duration of a peek-lock; that is, the amount of time that the
|
|
// message is locked for other receivers. The maximum value for LockDuration is 5 minutes; the default value is 1
|
|
// minute.
|
|
func QueueEntityWithLockDuration(window *time.Duration) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
if window == nil {
|
|
duration := time.Duration(1 * time.Minute)
|
|
window = &duration
|
|
}
|
|
q.LockDuration = ptrString(durationTo8601Seconds(*window))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithAutoForward configures the queue to automatically forward messages to the specified target.
|
|
//
|
|
// The ability to AutoForward to a target requires the connection have management authorization. If the connection
|
|
// string or Azure Active Directory identity used does not have management authorization, an unauthorized error will be
|
|
// returned on the PUT.
|
|
func QueueEntityWithAutoForward(target Targetable) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
uri := target.TargetURI()
|
|
q.ForwardTo = &uri
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithForwardDeadLetteredMessagesTo configures the queue to automatically forward dead letter messages to
|
|
// the specified target.
|
|
//
|
|
// The ability to forward dead letter messages to a target requires the connection have management authorization. If
|
|
// the connection string or Azure Active Directory identity used does not have management authorization, an unauthorized
|
|
// error will be returned on the PUT.
|
|
func QueueEntityWithForwardDeadLetteredMessagesTo(target Targetable) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
uri := target.TargetURI()
|
|
q.ForwardDeadLetteredMessagesTo = &uri
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QueueEntityWithMaxDeliveryCount configures the queue to have a maximum number of delivery attempts before
|
|
// dead-lettering the message
|
|
func QueueEntityWithMaxDeliveryCount(count int32) QueueManagementOption {
|
|
return func(q *QueueDescription) error {
|
|
q.MaxDeliveryCount = &count
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// NewQueueManager creates a new QueueManager for a Service Bus Namespace
|
|
func (ns *Namespace) NewQueueManager() *QueueManager {
|
|
return &QueueManager{
|
|
entityManager: newEntityManager(ns.getHTTPSHostURI(), ns.TokenProvider),
|
|
}
|
|
}
|
|
|
|
// Delete deletes a Service Bus Queue entity by name
|
|
func (qm *QueueManager) Delete(ctx context.Context, name string) error {
|
|
ctx, span := qm.startSpanFromContext(ctx, "sb.QueueManager.Delete")
|
|
defer span.End()
|
|
|
|
res, err := qm.entityManager.Delete(ctx, "/"+name)
|
|
defer closeRes(ctx, res)
|
|
|
|
return err
|
|
}
|
|
|
|
// Put creates or updates a Service Bus Queue
|
|
func (qm *QueueManager) Put(ctx context.Context, name string, opts ...QueueManagementOption) (*QueueEntity, error) {
|
|
ctx, span := qm.startSpanFromContext(ctx, "sb.QueueManager.Put")
|
|
defer span.End()
|
|
|
|
qd := new(QueueDescription)
|
|
for _, opt := range opts {
|
|
if err := opt(qd); err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
qd.ServiceBusSchema = to.StringPtr(serviceBusSchema)
|
|
|
|
qe := &queueEntry{
|
|
Entry: &atom.Entry{
|
|
AtomSchema: atomSchema,
|
|
},
|
|
Content: &queueContent{
|
|
Type: applicationXML,
|
|
QueueDescription: *qd,
|
|
},
|
|
}
|
|
|
|
var mw []MiddlewareFunc
|
|
if qd.ForwardTo != nil {
|
|
mw = append(mw, addSupplementalAuthorization(*qd.ForwardTo, qm.TokenProvider()))
|
|
}
|
|
|
|
if qd.ForwardDeadLetteredMessagesTo != nil {
|
|
mw = append(mw, addDeadLetterSupplementalAuthorization(*qd.ForwardDeadLetteredMessagesTo, qm.TokenProvider()))
|
|
}
|
|
|
|
reqBytes, err := xml.Marshal(qe)
|
|
if err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
reqBytes = xmlDoc(reqBytes)
|
|
res, err := qm.entityManager.Put(ctx, "/"+name, reqBytes, mw...)
|
|
defer closeRes(ctx, res)
|
|
|
|
if err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
var entry queueEntry
|
|
err = xml.Unmarshal(b, &entry)
|
|
if err != nil {
|
|
return nil, formatManagementError(b)
|
|
}
|
|
return queueEntryToEntity(&entry), nil
|
|
}
|
|
|
|
// List fetches all of the queues for a Service Bus Namespace
|
|
func (qm *QueueManager) List(ctx context.Context) ([]*QueueEntity, error) {
|
|
ctx, span := qm.startSpanFromContext(ctx, "sb.QueueManager.List")
|
|
defer span.End()
|
|
|
|
res, err := qm.entityManager.Get(ctx, `/$Resources/Queues`)
|
|
defer closeRes(ctx, res)
|
|
|
|
if err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
var feed queueFeed
|
|
err = xml.Unmarshal(b, &feed)
|
|
if err != nil {
|
|
return nil, formatManagementError(b)
|
|
}
|
|
|
|
qd := make([]*QueueEntity, len(feed.Entries))
|
|
for idx, entry := range feed.Entries {
|
|
qd[idx] = queueEntryToEntity(&entry)
|
|
}
|
|
return qd, nil
|
|
}
|
|
|
|
// Get fetches a Service Bus Queue entity by name
|
|
func (qm *QueueManager) Get(ctx context.Context, name string) (*QueueEntity, error) {
|
|
ctx, span := qm.startSpanFromContext(ctx, "sb.QueueManager.Get")
|
|
defer span.End()
|
|
|
|
res, err := qm.entityManager.Get(ctx, name)
|
|
defer closeRes(ctx, res)
|
|
|
|
if err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
if res.StatusCode == http.StatusNotFound {
|
|
return nil, ErrNotFound{EntityPath: res.Request.URL.Path}
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
tab.For(ctx).Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
var entry queueEntry
|
|
err = xml.Unmarshal(b, &entry)
|
|
if err != nil {
|
|
if isEmptyFeed(b) {
|
|
return nil, ErrNotFound{EntityPath: res.Request.URL.Path}
|
|
}
|
|
return nil, formatManagementError(b)
|
|
}
|
|
|
|
return queueEntryToEntity(&entry), nil
|
|
}
|