public async Task Should_route_to_redirect_route_if_exists()
        {
            const string redirectAddress = "a different destination";
            var          failedMessage   = await CreateFailedMessage();

            var message = CreateEditMessage(failedMessage.UniqueMessageId);

            using (var session = Store.OpenAsyncSession())
            {
                var redirects = await MessageRedirectsCollection.GetOrCreate(session);

                redirects.Redirects.Add(new MessageRedirect
                {
                    FromPhysicalAddress = failedMessage.ProcessingAttempts.Last().FailureDetails.AddressOfFailingEndpoint,
                    ToPhysicalAddress   = redirectAddress
                });
                await redirects.Save(session);
            }

            await Handler.Handle(message, new TestableInvokeHandlerContext());

            var sentMessage = Dispatcher.DispatchedMessages.Single().Item1;

            Assert.AreEqual(redirectAddress, sentMessage.Destination);
        }
Esempio n. 2
0
        public async Task <HttpResponseMessage> DeleteRedirect(Guid messageRedirectId)
        {
            using (var session = documentStore.OpenAsyncSession())
            {
                var redirects = await MessageRedirectsCollection.GetOrCreate(session).ConfigureAwait(false);

                var messageRedirect = redirects[messageRedirectId];

                if (messageRedirect == null)
                {
                    return(Request.CreateResponse(HttpStatusCode.NoContent));
                }

                redirects.Redirects.Remove(messageRedirect);

                await redirects.Save(session).ConfigureAwait(false);

                await domainEvents.Raise(new MessageRedirectRemoved
                {
                    MessageRedirectId   = messageRedirectId,
                    FromPhysicalAddress = messageRedirect.FromPhysicalAddress,
                    ToPhysicalAddress   = messageRedirect.ToPhysicalAddress
                }).ConfigureAwait(false);
            }

            return(Request.CreateResponse(HttpStatusCode.NoContent));
        }
Esempio n. 3
0
        public static IOrderedEnumerable <MessageRedirect> Sort(this MessageRedirectsCollection source, HttpRequestMessage request, string defaultSortDirection = "desc")
        {
            var query = request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);

            if (!query.TryGetValue("direction", out var direction))
            {
                direction = defaultSortDirection;
            }

            if (direction != "asc" && direction != "desc")
            {
                direction = defaultSortDirection;
            }

            if (!query.TryGetValue("sort", out var sort))
            {
                sort = "from_physical_address";
            }

            if (!SortOptions.Contains(sort))
            {
                sort = "from_physical_address";
            }

            if (sort == "to_physical_address")
            {
                return(direction == "asc" ? source.Redirects.OrderBy(r => r.ToPhysicalAddress) : source.Redirects.OrderByDescending(r => r.ToPhysicalAddress));
            }

            return(direction == "asc" ? source.Redirects.OrderBy(r => r.FromPhysicalAddress) : source.Redirects.OrderByDescending(r => r.FromPhysicalAddress));
        }
Esempio n. 4
0
        private bool MoveStagedBatchesToForwardingBatch(IDocumentSession session)
        {
            isRecoveringFromPrematureShutdown = false;

            var stagingBatch = session.Query <RetryBatch>()
                               .Customize(q => q.Include <RetryBatch, FailedMessageRetry>(b => b.FailureRetries))
                               .FirstOrDefault(b => b.Status == RetryBatchStatus.Staging);

            if (stagingBatch != null)
            {
                redirects = MessageRedirectsCollection.GetOrCreate(session);
                var stagedMessages  = Stage(stagingBatch, session);
                var skippedMessages = stagingBatch.InitialBatchSize - stagedMessages;
                retryingManager.Skip(stagingBatch.RequestId, stagingBatch.RetryType, skippedMessages);

                if (stagedMessages > 0)
                {
                    session.Store(new RetryBatchNowForwarding
                    {
                        RetryBatchId = stagingBatch.Id
                    }, RetryBatchNowForwarding.Id);
                }

                return(true);
            }

            return(false);
        }
Esempio n. 5
0
        public async Task <HttpResponseMessage> NewRedirects(MessageRedirectRequest request)
        {
            if (string.IsNullOrWhiteSpace(request.fromphysicaladdress) || string.IsNullOrWhiteSpace(request.tophysicaladdress))
            {
                return(Request.CreateResponse(HttpStatusCode.BadRequest));
            }

            var messageRedirect = new MessageRedirect
            {
                FromPhysicalAddress = request.fromphysicaladdress,
                ToPhysicalAddress   = request.tophysicaladdress,
                LastModifiedTicks   = DateTime.UtcNow.Ticks
            };

            using (var session = documentStore.OpenAsyncSession())
            {
                var collection = await MessageRedirectsCollection.GetOrCreate(session).ConfigureAwait(false);

                var existing = collection[messageRedirect.MessageRedirectId];

                if (existing != null)
                {
                    return(existing.ToPhysicalAddress == messageRedirect.ToPhysicalAddress
                        ? Negotiator.FromModel(Request, messageRedirect, HttpStatusCode.Created)
                        : Negotiator.FromModel(Request, existing, HttpStatusCode.Conflict).WithReasonPhrase("Duplicate"));
                }

                var dependents = collection.Redirects.Where(r => r.ToPhysicalAddress == request.fromphysicaladdress).ToList();

                if (dependents.Any())
                {
                    return(Negotiator.FromModel(Request, dependents, HttpStatusCode.Conflict).WithReasonPhrase("Dependents"));
                }

                collection.Redirects.Add(messageRedirect);

                await collection.Save(session).ConfigureAwait(false);
            }

            await domainEvents.Raise(new MessageRedirectCreated
            {
                MessageRedirectId   = messageRedirect.MessageRedirectId,
                FromPhysicalAddress = messageRedirect.FromPhysicalAddress,
                ToPhysicalAddress   = messageRedirect.ToPhysicalAddress
            }).ConfigureAwait(false);

            if (request.retryexisting)
            {
                await messageSession.SendLocal(new RetryPendingMessages
                {
                    QueueAddress = messageRedirect.FromPhysicalAddress,
                    PeriodFrom   = DateTime.MinValue,
                    PeriodTo     = DateTime.UtcNow
                }).ConfigureAwait(false);
            }

            return(Request.CreateResponse(HttpStatusCode.Created));
        }
Esempio n. 6
0
        public async Task <HttpResponseMessage> CountRedirects()
        {
            using (var session = documentStore.OpenAsyncSession())
            {
                var redirects = await MessageRedirectsCollection.GetOrCreate(session).ConfigureAwait(false);

                return(Request.CreateResponse(HttpStatusCode.OK)
                       .WithEtag(redirects.ETag)
                       .WithTotalCount(redirects.Redirects.Count));
            }
        }
Esempio n. 7
0
        static string ApplyRedirect(string addressOfFailingEndpoint, MessageRedirectsCollection redirects)
        {
            var redirect = redirects[addressOfFailingEndpoint];

            if (redirect != null)
            {
                addressOfFailingEndpoint = redirect.ToPhysicalAddress;
            }

            return(addressOfFailingEndpoint);
        }
        private async Task <bool> MoveStagedBatchesToForwardingBatch(IAsyncDocumentSession session)
        {
            try
            {
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Looking for batch to stage.");
                }

                isRecoveringFromPrematureShutdown = false;

                var stagingBatch = await session.Query <RetryBatch>()
                                   .Customize(q => q.Include <RetryBatch, FailedMessageRetry>(b => b.FailureRetries))
                                   .FirstOrDefaultAsync(b => b.Status == RetryBatchStatus.Staging)
                                   .ConfigureAwait(false);

                if (stagingBatch != null)
                {
                    Log.Info($"Staging batch {stagingBatch.Id}.");
                    redirects = await MessageRedirectsCollection.GetOrCreate(session).ConfigureAwait(false);

                    var stagedMessages = await Stage(stagingBatch, session).ConfigureAwait(false);

                    var skippedMessages = stagingBatch.InitialBatchSize - stagedMessages;
                    await retryingManager.Skip(stagingBatch.RequestId, stagingBatch.RetryType, skippedMessages)
                    .ConfigureAwait(false);

                    if (stagedMessages > 0)
                    {
                        Log.Info($"Batch {stagingBatch.Id} with {stagedMessages} messages staged and {skippedMessages} skipped ready to be forwarded.");
                        await session.StoreAsync(new RetryBatchNowForwarding
                        {
                            RetryBatchId = stagingBatch.Id
                        }, RetryBatchNowForwarding.Id)
                        .ConfigureAwait(false);
                    }

                    return(true);
                }

                Log.Info("No batch found to stage.");
                return(false);
            }
            catch (RetryStagingException)
            {
                return(true); //Execute another staging attempt immediately
            }
        }
Esempio n. 9
0
        public async Task <HttpResponseMessage> UpdateRedirect(Guid messageRedirectId, MessageRedirectRequest request)
        {
            if (string.IsNullOrWhiteSpace(request.tophysicaladdress))
            {
                return(Request.CreateResponse(HttpStatusCode.BadRequest));
            }

            using (var session = documentStore.OpenAsyncSession())
            {
                var redirects = await MessageRedirectsCollection.GetOrCreate(session).ConfigureAwait(false);

                var messageRedirect = redirects[messageRedirectId];

                if (messageRedirect == null)
                {
                    return(Request.CreateResponse(HttpStatusCode.NotFound));
                }

                var toMessageRedirectId = DeterministicGuid.MakeId(request.tophysicaladdress);

                if (redirects[toMessageRedirectId] != null)
                {
                    return(Request.CreateResponse(HttpStatusCode.Conflict));
                }

                var messageRedirectChanged = new MessageRedirectChanged
                {
                    MessageRedirectId         = messageRedirectId,
                    PreviousToPhysicalAddress = messageRedirect.ToPhysicalAddress,
                    FromPhysicalAddress       = messageRedirect.FromPhysicalAddress,
                    ToPhysicalAddress         = messageRedirect.ToPhysicalAddress = request.tophysicaladdress
                };

                messageRedirect.LastModifiedTicks = DateTime.UtcNow.Ticks;

                await redirects.Save(session).ConfigureAwait(false);

                await domainEvents.Raise(messageRedirectChanged)
                .ConfigureAwait(false);

                return(Request.CreateResponse(HttpStatusCode.NoContent));
            }
        }
        public static IOrderedEnumerable <MessageRedirect> Sort(this MessageRedirectsCollection source, Request request, string defaultSortDirection = "desc")
        {
            var direction = defaultSortDirection;

            if (request.Query.direction.HasValue)
            {
                direction = (string)request.Query.direction;
            }

            if (direction != "asc" && direction != "desc")
            {
                direction = defaultSortDirection;
            }

            var sortOptions = new[]
            {
                "from_physical_address",
                "to_physical_address"
            };

            var sort = "from_physical_address";

            if (request.Query.sort.HasValue)
            {
                sort = (string)request.Query.sort;
            }

            if (!sortOptions.Contains(sort))
            {
                sort = "from_physical_address";
            }

            if (sort == "to_physical_address")
            {
                return(direction == "asc" ? source.Redirects.OrderBy(r => r.ToPhysicalAddress) : source.Redirects.OrderByDescending(r => r.ToPhysicalAddress));
            }

            return(direction == "asc" ? source.Redirects.OrderBy(r => r.FromPhysicalAddress) : source.Redirects.OrderByDescending(r => r.FromPhysicalAddress));
        }
Esempio n. 11
0
        public async Task <HttpResponseMessage> Redirects()
        {
            using (var session = documentStore.OpenAsyncSession())
            {
                var redirects = await MessageRedirectsCollection.GetOrCreate(session).ConfigureAwait(false);

                var queryResult = redirects
                                  .Sort(Request)
                                  .Paging(Request)
                                  .Select(r => new
                {
                    r.MessageRedirectId,
                    r.FromPhysicalAddress,
                    r.ToPhysicalAddress,
                    LastModified = new DateTime(r.LastModifiedTicks)
                });

                return(Negotiator
                       .FromModel(Request, queryResult)
                       .WithEtag(redirects.ETag)
                       .WithPagingLinksAndTotalCount(redirects.Redirects.Count, Request));
            }
        }
        public MessageRedirectsModule()
        {
            Post["/redirects"] = parameters =>
            {
                var request = this.Bind <MessageRedirectRequest>();

                if (string.IsNullOrWhiteSpace(request.fromphysicaladdress) || string.IsNullOrWhiteSpace(request.tophysicaladdress))
                {
                    return(HttpStatusCode.BadRequest);
                }

                var messageRedirect = new MessageRedirect
                {
                    FromPhysicalAddress = request.fromphysicaladdress,
                    ToPhysicalAddress   = request.tophysicaladdress,
                    LastModifiedTicks   = DateTime.UtcNow.Ticks
                };

                using (var session = Store.OpenSession())
                {
                    var collection = MessageRedirectsCollection.GetOrCreate(session);

                    var existing = collection[messageRedirect.MessageRedirectId];

                    if (existing != null)
                    {
                        return(existing.ToPhysicalAddress == messageRedirect.ToPhysicalAddress
                            ? Negotiate.WithModel(messageRedirect).WithStatusCode(HttpStatusCode.Created)
                            : Negotiate.WithReasonPhrase("Duplicate").WithModel(existing).WithStatusCode(HttpStatusCode.Conflict));
                    }

                    var dependents = collection.Redirects.Where(r => r.ToPhysicalAddress == request.fromphysicaladdress).ToList();

                    if (dependents.Any())
                    {
                        return(Negotiate.WithReasonPhrase("Dependents").WithModel(dependents).WithStatusCode(HttpStatusCode.Conflict));
                    }

                    collection.Redirects.Add(messageRedirect);

                    collection.Save(session);
                }

                Bus.Publish(new MessageRedirectCreated
                {
                    MessageRedirectId   = messageRedirect.MessageRedirectId,
                    FromPhysicalAddress = messageRedirect.FromPhysicalAddress,
                    ToPhysicalAddress   = messageRedirect.ToPhysicalAddress
                });

                if (request.retryexisting)
                {
                    Bus.SendLocal(new RetryPendingMessages
                    {
                        QueueAddress = messageRedirect.FromPhysicalAddress,
                        PeriodFrom   = DateTime.MinValue,
                        PeriodTo     = DateTime.UtcNow
                    });
                }

                return(HttpStatusCode.Created);
            };

            Put["/redirects/{messageredirectid:guid}/"] = parameters =>
            {
                Guid messageRedirectId = parameters.messageredirectid;

                var request = this.Bind <MessageRedirectRequest>();

                if (string.IsNullOrWhiteSpace(request.tophysicaladdress))
                {
                    return(HttpStatusCode.BadRequest);
                }

                using (var session = Store.OpenSession())
                {
                    var redirects = MessageRedirectsCollection.GetOrCreate(session);

                    var messageRedirect = redirects[messageRedirectId];

                    if (messageRedirect == null)
                    {
                        return(HttpStatusCode.NotFound);
                    }

                    var toMessageRedirectId = DeterministicGuid.MakeId(request.tophysicaladdress);

                    if (redirects[toMessageRedirectId] != null)
                    {
                        return(HttpStatusCode.Conflict);
                    }

                    var messageRedirectChanged = new MessageRedirectChanged
                    {
                        MessageRedirectId         = messageRedirectId,
                        PreviousToPhysicalAddress = messageRedirect.ToPhysicalAddress,
                        FromPhysicalAddress       = messageRedirect.FromPhysicalAddress,
                        ToPhysicalAddress         = messageRedirect.ToPhysicalAddress = request.tophysicaladdress,
                    };

                    messageRedirect.LastModifiedTicks = DateTime.UtcNow.Ticks;

                    redirects.Save(session);

                    Bus.Publish(messageRedirectChanged);

                    return(HttpStatusCode.NoContent);
                }
            };

            Delete["/redirects/{messageredirectid:guid}/"] = parameters =>
            {
                Guid messageRedirectId = parameters.messageredirectid;

                using (var session = Store.OpenSession())
                {
                    var redirects = MessageRedirectsCollection.GetOrCreate(session);

                    var messageRedirect = redirects[messageRedirectId];

                    if (messageRedirect == null)
                    {
                        return(HttpStatusCode.NoContent);
                    }

                    redirects.Redirects.Remove(messageRedirect);

                    redirects.Save(session);

                    Bus.Publish <MessageRedirectRemoved>(evt =>
                    {
                        evt.MessageRedirectId   = messageRedirectId;
                        evt.FromPhysicalAddress = messageRedirect.FromPhysicalAddress;
                        evt.ToPhysicalAddress   = messageRedirect.ToPhysicalAddress;
                    });
                }

                return(HttpStatusCode.NoContent);
            };

            Head["/redirects"] = _ =>
            {
                using (var session = Store.OpenSession())
                {
                    var redirects = MessageRedirectsCollection.GetOrCreate(session);

                    return(Negotiate
                           .WithEtagAndLastModified(redirects.ETag, redirects.LastModified)
                           .WithTotalCount(redirects.Redirects.Count));
                }
            };

            Get["/redirects"] = _ =>
            {
                using (var session = Store.OpenSession())
                {
                    var redirects = MessageRedirectsCollection.GetOrCreate(session);

                    var queryResult = redirects
                                      .Sort(Request)
                                      .Paging(Request)
                                      .Select(r => new
                    {
                        r.MessageRedirectId,
                        r.FromPhysicalAddress,
                        r.ToPhysicalAddress,
                        LastModified = new DateTime(r.LastModifiedTicks)
                    });

                    return(Negotiate
                           .WithModel(queryResult)
                           .WithEtagAndLastModified(redirects.ETag, redirects.LastModified)
                           .WithPagingLinksAndTotalCount(redirects.Redirects.Count, Request));
                }
            };
        }
Esempio n. 13
0
        public async Task Handle(EditAndSend message, IMessageHandlerContext context)
        {
            FailedMessage failedMessage;
            MessageRedirectsCollection redirects;

            using (var session = store.OpenAsyncSession())
            {
                failedMessage = await session.LoadAsync <FailedMessage>(FailedMessage.MakeDocumentId(message.FailedMessageId))
                                .ConfigureAwait(false);

                if (failedMessage == null)
                {
                    log.WarnFormat("Discarding edit {0} because no message failure for id {1} has been found.", context.MessageId, message.FailedMessageId);
                    return;
                }

                var edit = await session.LoadAsync <FailedMessageEdit>(FailedMessageEdit.MakeDocumentId(message.FailedMessageId))
                           .ConfigureAwait(false);

                if (edit == null)
                {
                    if (failedMessage.Status != FailedMessageStatus.Unresolved)
                    {
                        log.WarnFormat("Discarding edit {0} because message failure {1} doesn't have state 'Unresolved'.", context.MessageId, message.FailedMessageId);
                        return;
                    }

                    // create a retries document to prevent concurrent edits
                    await session.StoreAsync(new FailedMessageEdit
                    {
                        Id = FailedMessageEdit.MakeDocumentId(message.FailedMessageId),
                        FailedMessageId = message.FailedMessageId,
                        EditId          = context.MessageId
                    }, Etag.Empty).ConfigureAwait(false);
                }
                else if (edit.EditId != context.MessageId)
                {
                    log.WarnFormat("Discarding edit {0} because the failure ({1}) has already been edited by edit {2}", context.MessageId, message.FailedMessageId, edit.EditId);
                    return;
                }

                // the original failure is marked as resolved as any failures of the edited message are treated as a new message failure.
                failedMessage.Status = FailedMessageStatus.Resolved;

                redirects = await MessageRedirectsCollection.GetOrCreate(session)
                            .ConfigureAwait(false);

                await session.SaveChangesAsync().ConfigureAwait(false);
            }

            var attempt = failedMessage.ProcessingAttempts.Last();

            var outgoingMessage = BuildMessage(message);

            // mark the new message with a link to the original message id
            outgoingMessage.Headers.Add("ServiceControl.EditOf", message.FailedMessageId);
            var address = ApplyRedirect(attempt.FailureDetails.AddressOfFailingEndpoint, redirects);

            await DispatchEditedMessage(outgoingMessage, address, context)
            .ConfigureAwait(false);
        }