Beispiel #1
0
        public async Task GetChangeFeedTokensAsync_DrainFromJustOnePartition()
        {
            int pkRangesCount = (await this.LargerContainer.ClientContext.DocumentClient.ReadPartitionKeyRangeFeedAsync(this.LargerContainer.LinkUri)).Count;
            ContainerInternal    itemsCore = this.LargerContainer;
            IEnumerable <string> tokens    = await itemsCore.GetChangeFeedTokensAsync();

            Assert.IsTrue(pkRangesCount > 1, "Should have created a multi partition container.");
            Assert.AreEqual(pkRangesCount, tokens.Count());
            int totalDocuments = 200;

            await this.CreateRandomItems(this.LargerContainer, totalDocuments, randomPartitionKey : true);

            string token = tokens.First();

            int          count            = 0;
            FeedIterator iteratorForToken =
                itemsCore.GetStandByFeedIterator(
                    ChangeFeedStartFrom.ContinuationToken(token));

            while (iteratorForToken.HasMoreResults)
            {
                using (ResponseMessage responseMessage = await iteratorForToken.ReadNextAsync(this.cancellationToken))
                {
                    if (!responseMessage.IsSuccessStatusCode)
                    {
                        break;
                    }

                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                    count += response.Count;
                }
            }

            Assert.IsTrue(count > 0 && count < totalDocuments);
        }
Beispiel #2
0
        private static async Task <int> DrainWithUntilNotModifiedWithContinuationTokens(
            IDocumentContainer documentContainer,
            CrossPartitionChangeFeedAsyncEnumerator enumerator)
        {
            List <CosmosElement> globalChanges = new List <CosmosElement>();

            while (true)
            {
                if (!await enumerator.MoveNextAsync())
                {
                    throw new InvalidOperationException();
                }

                Assert.IsTrue(enumerator.Current.Succeeded);

                if (!(enumerator.Current.Result is ChangeFeedSuccessPage changeFeedSuccessPage))
                {
                    break;
                }

                CosmosArray changes = GetChanges(changeFeedSuccessPage.Content);
                globalChanges.AddRange(changes);

                CosmosElement continuationToken = ((ChangeFeedStateContinuation)enumerator.Current.Result.State).ContinuationToken;

                enumerator = CrossPartitionChangeFeedAsyncEnumerator.MonadicCreate(
                    documentContainer,
                    new ChangeFeedRequestOptions(),
                    ChangeFeedStartFrom.ContinuationToken(continuationToken.ToString()),
                    cancellationToken: default).Result;
            }

            return(globalChanges.Count);
        }
Beispiel #3
0
        private static async Task ReadFeedJson <TEventHandler>(Container container, TEventHandler eventHandler, int pageSizeHint, string?continuationToken, JsonSerializerOptions options, CancellationToken cancellationToken)
            where TEventHandler : IJsonEventFeedHandler
        {
            var requestOptions = new ChangeFeedRequestOptions {
                PageSizeHint = pageSizeHint
            };
            FeedIterator iterator = container.GetChangeFeedStreamIterator(
                continuationToken is null ? ChangeFeedStartFrom.Beginning() : ChangeFeedStartFrom.ContinuationToken(continuationToken),
                ChangeFeedMode.Incremental,
                requestOptions);

            while (iterator.HasMoreResults && !cancellationToken.IsCancellationRequested)
            {
                using ResponseMessage response = await iterator.ReadNextAsync(cancellationToken).ConfigureAwait(false);

                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                response.EnsureSuccessStatusCode();

                CosmosEventStore <TContainerFactory, TSnapshotReader> .ProcessEventFeedStream(eventHandler, response.Content, options.DefaultBufferSize, cancellationToken);

                await eventHandler.HandleBatchComplete(response.ContinuationToken).ConfigureAwait(false);
            }
        }
Beispiel #4
0
        public async Task ChangeFeedIteratorCore_ReadAll()
        {
            int totalCount    = 0;
            int firstRunTotal = 25;
            int batchSize     = 25;

            ContainerInternal itemsCore = await this.InitializeLargeContainerAsync();

            await this.CreateRandomItems(itemsCore, batchSize, randomPartitionKey : true);

            ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.Beginning(),
                ChangeFeedMode.Incremental) as ChangeFeedIteratorCore;
            string continuation = null;

            while (feedIterator.HasMoreResults)
            {
                using (ResponseMessage responseMessage =
                           await feedIterator.ReadNextAsync(this.cancellationToken))
                {
                    if (!responseMessage.IsSuccessStatusCode)
                    {
                        continuation = responseMessage.ContinuationToken;
                        break;
                    }

                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                    totalCount += response.Count;
                }
            }

            Assert.AreEqual(firstRunTotal, totalCount);

            int expectedFinalCount = 50;

            // Insert another batch of 25 and use the last FeedToken from the first cycle
            await this.CreateRandomItems(itemsCore, batchSize, randomPartitionKey : true);

            ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.ContinuationToken(continuation),
                ChangeFeedMode.Incremental) as ChangeFeedIteratorCore;

            while (setIteratorNew.HasMoreResults)
            {
                using (ResponseMessage responseMessage =
                           await setIteratorNew.ReadNextAsync(this.cancellationToken))
                {
                    if (!responseMessage.IsSuccessStatusCode)
                    {
                        break;
                    }

                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                    totalCount += response.Count;
                }
            }

            Assert.AreEqual(expectedFinalCount, totalCount);
        }
Beispiel #5
0
        public async Task ChangeFeedIteratorCore_NoFetchNext()
        {
            ContainerInternal itemsCore = await this.InitializeLargeContainerAsync();

            int pkRangesCount = (await itemsCore.ClientContext.DocumentClient.ReadPartitionKeyRangeFeedAsync(itemsCore.LinkUri)).Count;

            int expected   = 25;
            int iterations = 0;

            await this.CreateRandomItems(itemsCore, expected, randomPartitionKey : true);

            string continuation = null;
            int    count        = 0;

            while (true)
            {
                ChangeFeedStartFrom startFrom;
                if (continuation == null)
                {
                    startFrom = ChangeFeedStartFrom.Beginning();
                }
                else
                {
                    startFrom = ChangeFeedStartFrom.ContinuationToken(continuation);
                }

                ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(startFrom, ChangeFeedMode.Incremental) as ChangeFeedIteratorCore;
                using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken))
                {
                    if (responseMessage.IsSuccessStatusCode)
                    {
                        Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                        count += response.Count;
                    }
                    else
                    {
                        if (responseMessage.StatusCode != HttpStatusCode.NotModified)
                        {
                            Assert.Fail(responseMessage.ErrorMessage);
                        }
                    }

                    continuation = responseMessage.ContinuationToken;
                }

                if (count.Equals(expected))
                {
                    break;
                }

                if (iterations++ > pkRangesCount)
                {
                    Assert.Fail("" +
                                "Feed does not contain all elements even after looping through PK ranges. " +
                                "Either the continuation is not moving forward or there is some state problem.");
                }
            }
        }
        public async Task CannotMixTokensFromOtherContainers()
        {
            IReadOnlyList <FeedRange> tokens = await this.LargerContainer.GetFeedRangesAsync();

            FeedIterator iterator = this.LargerContainer.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.Beginning(tokens[0]));
            ResponseMessage responseMessage = await iterator.ReadNextAsync();

            iterator = this.Container.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.ContinuationToken(responseMessage.ContinuationToken));
            responseMessage = await iterator.ReadNextAsync();

            Assert.IsNotNull(responseMessage.CosmosException);
            Assert.AreEqual(HttpStatusCode.BadRequest, responseMessage.StatusCode);
        }
Beispiel #7
0
        public async Task GetChangeFeedTokensAsync_AllowsParallelProcessing()
        {
            int pkRangesCount = (await this.LargerContainer.ClientContext.DocumentClient.ReadPartitionKeyRangeFeedAsync(this.LargerContainer.LinkUri)).Count;
            ContainerInternal    itemsCore = this.LargerContainer;
            IEnumerable <string> tokens    = await itemsCore.GetChangeFeedTokensAsync();

            Assert.IsTrue(pkRangesCount > 1, "Should have created a multi partition container.");
            Assert.AreEqual(pkRangesCount, tokens.Count());
            int totalDocuments = 200;

            await this.CreateRandomItems(this.LargerContainer, totalDocuments, randomPartitionKey : true);

            List <Task <int> > tasks = tokens.Select(token => Task.Run(async() =>
            {
                int count = 0;
                FeedIterator iteratorForToken =
                    itemsCore.GetStandByFeedIterator(
                        ChangeFeedStartFrom.ContinuationToken(token));
                while (true)
                {
                    using (ResponseMessage responseMessage =
                               await iteratorForToken.ReadNextAsync(this.cancellationToken))
                    {
                        if (!responseMessage.IsSuccessStatusCode)
                        {
                            break;
                        }

                        Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                        count += response.Count;
                    }
                }

                return(count);
            })).ToList();

            await Task.WhenAll(tasks);

            int documentsRead = 0;

            foreach (Task <int> task in tasks)
            {
                documentsRead += task.Result;
            }

            Assert.AreEqual(totalDocuments, documentsRead);
        }
Beispiel #8
0
        public async Task ChangeFeedIteratorCore_CannotMixTokensFromOtherContainers()
        {
            ContainerInternal oneContainer = await this.InitializeContainerAsync();

            ContainerInternal otherContainer = await this.InitializeContainerAsync();

            IReadOnlyList <FeedRange> tokens = await oneContainer.GetFeedRangesAsync();

            FeedIterator iterator = oneContainer.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.Beginning(tokens[0]),
                ChangeFeedMode.Incremental);
            ResponseMessage responseMessage = await iterator.ReadNextAsync();

            iterator = otherContainer.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.ContinuationToken(responseMessage.ContinuationToken),
                ChangeFeedMode.Incremental);
            responseMessage = await iterator.ReadNextAsync();

            Assert.IsNotNull(responseMessage.CosmosException);
            Assert.AreEqual(HttpStatusCode.BadRequest, responseMessage.StatusCode);
        }
Beispiel #9
0
        public async Task StandByFeedIterator_WithInexistentRange()
        {
            // Add some random range, this will force the failure
            List <CompositeContinuationToken> corruptedTokens = new List <CompositeContinuationToken>
            {
                new CompositeContinuationToken()
                {
                    Range = new Documents.Routing.Range <string>("whatever", "random", true, false),
                    Token = "oops"
                }
            };

            string corruptedTokenSerialized = JsonConvert.SerializeObject(corruptedTokens);

            ContainerInternal itemsCore      = this.Container;
            FeedIterator      setIteratorNew =
                itemsCore.GetStandByFeedIterator(
                    ChangeFeedStartFrom.ContinuationToken(corruptedTokenSerialized));

            _ = await setIteratorNew.ReadNextAsync(this.cancellationToken);

            Assert.Fail("Should have thrown.");
        }
Beispiel #10
0
        public async Task ChangeFeed_FeedRange_FromV0Token()
        {
            ContainerResponse largerContainer = await this.database.CreateContainerAsync(
                new ContainerProperties(id : Guid.NewGuid().ToString(), partitionKeyPath : "/pk"),
                throughput : 20000,
                cancellationToken : this.cancellationToken);

            ContainerInternal container = (ContainerInlineCore)largerContainer;

            int expected = 100;
            int count    = 0;

            await this.CreateRandomItems((ContainerCore)container, expected, randomPartitionKey : true);

            IReadOnlyList <FeedRange> feedRanges = await container.GetFeedRangesAsync();

            List <string> continuations = new List <string>();

            // First do one request to construct the old model information based on Etag
            foreach (FeedRange feedRange in feedRanges)
            {
                IEnumerable <string> pkRangeIds = await container.GetPartitionKeyRangesAsync(feedRange);

                ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions()
                {
                    PageSizeHint = 1
                };
                ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(
                    changeFeedStartFrom: ChangeFeedStartFrom.Beginning(feedRange),
                    changeFeedMode: ChangeFeedMode.Incremental,
                    changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore;
                ResponseMessage firstResponse = await feedIterator.ReadNextAsync();

                // Construct the continuation's range, using PKRangeId + ETag
                List <dynamic> ct = new List <dynamic>()
                {
                    new
                    {
                        FeedRange = new
                        {
                            type  = "Physical Partition Key Range Id",
                            value = pkRangeIds.First()
                        },
                        State = new
                        {
                            type  = "continuation",
                            value = JObject.Parse(firstResponse.ContinuationToken)["Continuation"][0]["State"]["value"].ToString()
                        }
                    }
                };

                if (firstResponse.Content != null)
                {
                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(firstResponse.Content).Data;
                    count += response.Count;
                }

                // Extract Etag and manually construct the continuation
                dynamic oldContinuation = new
                {
                    V            = 2,
                    Rid          = await container.GetCachedRIDAsync(cancellationToken : this.cancellationToken),
                    Continuation = ct
                };
                continuations.Add(JsonConvert.SerializeObject(oldContinuation));
            }

            // Now start the new iterators with the constructed continuations from migration
            foreach (string continuation in continuations)
            {
                ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions()
                {
                    PageSizeHint = 100,
                };
                ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(
                    changeFeedStartFrom: ChangeFeedStartFrom.ContinuationToken(continuation),
                    changeFeedMode: ChangeFeedMode.Incremental,
                    changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore;
                ResponseMessage firstResponse = await feedIterator.ReadNextAsync();

                if (firstResponse.IsSuccessStatusCode)
                {
                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(firstResponse.Content).Data;
                    count += response.Count;
                    string migratedContinuation = firstResponse.ContinuationToken;
                    TryCatch <CosmosElement> monadicParsedToken = CosmosElement.Monadic.Parse(migratedContinuation);
                    Assert.IsFalse(monadicParsedToken.Failed);
                    TryCatch <VersionedAndRidCheckedCompositeToken> monadicVersionedToken = VersionedAndRidCheckedCompositeToken
                                                                                            .MonadicCreateFromCosmosElement(monadicParsedToken.Result);
                    Assert.IsFalse(monadicVersionedToken.Failed);
                    VersionedAndRidCheckedCompositeToken versionedAndRidCheckedCompositeToken = monadicVersionedToken.Result;
                    Assert.AreEqual(VersionedAndRidCheckedCompositeToken.Version.V2, versionedAndRidCheckedCompositeToken.VersionNumber);
                }
            }

            Assert.AreEqual(expected, count);
        }
Beispiel #11
0
        public async Task ChangeFeedIteratorCore_OfT_ReadAll()
        {
            int totalCount    = 0;
            int firstRunTotal = 25;
            int batchSize     = 25;

            ContainerInternal itemsCore = await this.InitializeContainerAsync();

            await this.CreateRandomItems(itemsCore, batchSize, randomPartitionKey : true);

            FeedIterator <ToDoActivity> feedIterator = itemsCore.GetChangeFeedIterator <ToDoActivity>(ChangeFeedStartFrom.Beginning(), ChangeFeedMode.Incremental);
            string continuation = null;

            while (feedIterator.HasMoreResults)
            {
                try
                {
                    FeedResponse <ToDoActivity> feedResponse = await feedIterator.ReadNextAsync(this.cancellationToken);

                    totalCount  += feedResponse.Count;
                    continuation = feedResponse.ContinuationToken;
                }
                catch (CosmosException cosmosException) when(cosmosException.StatusCode == HttpStatusCode.NotModified)
                {
                    continuation = cosmosException.Headers.ContinuationToken;
                    break;
                }
            }

            Assert.AreEqual(firstRunTotal, totalCount);

            int expectedFinalCount = 50;

            // Insert another batch of 25 and use the last FeedToken from the first cycle
            await this.CreateRandomItems(itemsCore, batchSize, randomPartitionKey : true);

            FeedIterator <ToDoActivity> setIteratorNew = itemsCore.GetChangeFeedIterator <ToDoActivity>(ChangeFeedStartFrom.ContinuationToken(continuation), ChangeFeedMode.Incremental);

            while (setIteratorNew.HasMoreResults)
            {
                try
                {
                    FeedResponse <ToDoActivity> feedResponse = await feedIterator.ReadNextAsync(this.cancellationToken);

                    totalCount += feedResponse.Count;
                }
                catch (CosmosException cosmosException) when(cosmosException.StatusCode == HttpStatusCode.NotModified)
                {
                    break;
                }
            }

            Assert.AreEqual(expectedFinalCount, totalCount);
        }
Beispiel #12
0
        public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll()
        {
            int totalCount    = 0;
            int firstRunTotal = 25;
            int batchSize     = 25;

            string pkToRead = "pkToRead";
            string otherPK  = "otherPK";

            ContainerInternal itemsCore = await this.InitializeContainerAsync();

            for (int i = 0; i < batchSize; i++)
            {
                await itemsCore.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(pk: pkToRead));
            }

            for (int i = 0; i < batchSize; i++)
            {
                await itemsCore.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(pk: otherPK));
            }

            FeedIterator <ToDoActivity> feedIterator = itemsCore.GetChangeFeedIterator <ToDoActivity>(
                ChangeFeedStartFrom.Beginning(
                    new FeedRangePartitionKey(
                        new PartitionKey(pkToRead))),
                ChangeFeedMode.Incremental,
                new ChangeFeedRequestOptions()
            {
                PageSizeHint = 1,
            });
            string continuation = null;

            while (feedIterator.HasMoreResults)
            {
                try
                {
                    FeedResponse <ToDoActivity> feedResponse = await feedIterator.ReadNextAsync(this.cancellationToken);

                    totalCount += feedResponse.Count;
                    foreach (ToDoActivity toDoActivity in feedResponse)
                    {
                        Assert.AreEqual(pkToRead, toDoActivity.pk);
                    }

                    continuation = feedResponse.ContinuationToken;
                }
                catch (CosmosException cosmosException) when(cosmosException.StatusCode == HttpStatusCode.NotModified)
                {
                    continuation = cosmosException.Headers.ContinuationToken;
                    break;
                }
            }

            Assert.AreEqual(firstRunTotal, totalCount);

            int expectedFinalCount = 50;

            // Insert another batch of 25 and use the last FeedToken from the first cycle
            for (int i = 0; i < batchSize; i++)
            {
                await itemsCore.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(pk: pkToRead));
            }

            FeedIterator <ToDoActivity> setIteratorNew = itemsCore.GetChangeFeedIterator <ToDoActivity>(
                ChangeFeedStartFrom.ContinuationToken(continuation),
                ChangeFeedMode.Incremental);

            while (setIteratorNew.HasMoreResults)
            {
                try
                {
                    FeedResponse <ToDoActivity> feedResponse = await setIteratorNew.ReadNextAsync(this.cancellationToken);

                    totalCount += feedResponse.Count;
                    foreach (ToDoActivity toDoActivity in feedResponse)
                    {
                        Assert.AreEqual(pkToRead, toDoActivity.pk);
                    }
                }
                catch (CosmosException cosmosException) when(cosmosException.StatusCode == HttpStatusCode.NotModified)
                {
                    break;
                }
            }

            Assert.AreEqual(expectedFinalCount, totalCount);
        }
Beispiel #13
0
        public async Task ChangeFeedIteratorCore_PartitionKey_ReadAll()
        {
            int totalCount    = 0;
            int firstRunTotal = 25;
            int batchSize     = 25;

            string pkToRead = "pkToRead";
            string otherPK  = "otherPK";

            ContainerInternal itemsCore = await this.InitializeContainerAsync();

            for (int i = 0; i < batchSize; i++)
            {
                await itemsCore.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(pk: pkToRead));
            }

            for (int i = 0; i < batchSize; i++)
            {
                await itemsCore.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(pk: otherPK));
            }

            ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.Beginning(
                    FeedRange.FromPartitionKey(
                        new PartitionKey(pkToRead))),
                ChangeFeedMode.Incremental,
                new ChangeFeedRequestOptions()
            {
                PageSizeHint = 1,
            }) as ChangeFeedIteratorCore;
            string continuation = null;

            while (feedIterator.HasMoreResults)
            {
                using (ResponseMessage responseMessage =
                           await feedIterator.ReadNextAsync(this.cancellationToken))
                {
                    if (!responseMessage.IsSuccessStatusCode)
                    {
                        continuation = responseMessage.ContinuationToken;
                        break;
                    }

                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                    totalCount += response.Count;
                    foreach (ToDoActivity toDoActivity in response)
                    {
                        Assert.AreEqual(pkToRead, toDoActivity.pk);
                    }
                }
            }

            Assert.AreEqual(firstRunTotal, totalCount);

            int expectedFinalCount = 50;

            // Insert another batch of 25 and use the last FeedToken from the first cycle
            for (int i = 0; i < batchSize; i++)
            {
                await itemsCore.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(pk: pkToRead));
            }

            ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator(
                ChangeFeedStartFrom.ContinuationToken(continuation),
                ChangeFeedMode.Incremental) as ChangeFeedIteratorCore;

            while (setIteratorNew.HasMoreResults)
            {
                using (ResponseMessage responseMessage =
                           await setIteratorNew.ReadNextAsync(this.cancellationToken))
                {
                    if (!responseMessage.IsSuccessStatusCode)
                    {
                        break;
                    }

                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                    totalCount += response.Count;
                    foreach (ToDoActivity toDoActivity in response)
                    {
                        Assert.AreEqual(pkToRead, toDoActivity.pk);
                    }
                }
            }

            Assert.AreEqual(expectedFinalCount, totalCount);
        }
        public async Task ChangeFeedIteratorCore_OfT_ReadAll()
        {
            int totalCount    = 0;
            int firstRunTotal = 25;
            int batchSize     = 25;

            await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey : true);

            ContainerInternal           itemsCore    = this.Container;
            FeedIterator <ToDoActivity> feedIterator = itemsCore.GetChangeFeedIterator <ToDoActivity>(ChangeFeedStartFrom.Beginning());
            string continuation = null;

            while (feedIterator.HasMoreResults)
            {
                FeedResponse <ToDoActivity> feedResponse = await feedIterator.ReadNextAsync(this.cancellationToken);

                totalCount  += feedResponse.Count;
                continuation = feedResponse.ContinuationToken;
            }

            Assert.AreEqual(firstRunTotal, totalCount);

            int expectedFinalCount = 50;

            // Insert another batch of 25 and use the last FeedToken from the first cycle
            await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey : true);

            FeedIterator <ToDoActivity> setIteratorNew = itemsCore.GetChangeFeedIterator <ToDoActivity>(ChangeFeedStartFrom.ContinuationToken(continuation));

            while (setIteratorNew.HasMoreResults)
            {
                FeedResponse <ToDoActivity> feedResponse = await setIteratorNew.ReadNextAsync(this.cancellationToken);

                totalCount += feedResponse.Count;
            }

            Assert.AreEqual(expectedFinalCount, totalCount);
        }
Beispiel #15
0
        internal async Task <(string, ResponseMessage)> ReadNextInternalAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (this.compositeContinuationToken == null)
            {
                PartitionKeyRangeCache pkRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

                this.containerRid = await this.container.GetRIDAsync(cancellationToken);

                if (this.changeFeedStartFrom is ChangeFeedStartFromContinuation startFromContinuation)
                {
                    this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync(
                        this.containerRid,
                        startFromContinuation.Continuation,
                        pkRangeCache.TryGetOverlappingRangesAsync);

                    (CompositeContinuationToken token, string id) = await this.compositeContinuationToken.GetCurrentTokenAsync();

                    if (token.Token != null)
                    {
                        this.changeFeedStartFrom = ChangeFeedStartFrom.ContinuationToken(token.Token);
                    }
                    else
                    {
                        this.changeFeedStartFrom = ChangeFeedStartFrom.Beginning();
                    }
                }
                else
                {
                    this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync(
                        this.containerRid,
                        initialStandByFeedContinuationToken : null,
                        pkRangeCache.TryGetOverlappingRangesAsync);
                }
            }

            (CompositeContinuationToken currentRangeToken, string rangeId) = await this.compositeContinuationToken.GetCurrentTokenAsync();

            FeedRange feedRange = new FeedRangePartitionKeyRange(rangeId);

            if (currentRangeToken.Token != null)
            {
                this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(currentRangeToken.Token, (FeedRangeInternal)feedRange);
            }
            else
            {
                this.changeFeedStartFrom = ChangeFeedStartFrom.Beginning(feedRange);
            }

            ResponseMessage response = await this.NextResultSetDelegateAsync(this.changeFeedOptions, cancellationToken);

            if (await this.ShouldRetryFailureAsync(response, cancellationToken))
            {
                return(await this.ReadNextInternalAsync(cancellationToken));
            }

            if (response.IsSuccessStatusCode ||
                response.StatusCode == HttpStatusCode.NotModified)
            {
                // Change Feed read uses Etag for continuation
                currentRangeToken.Token = response.Headers.ETag;
            }

            return(rangeId, response);
        }
Beispiel #16
0
        public ChangeFeedIteratorCore(
            IDocumentContainer documentContainer,
            ChangeFeedRequestOptions changeFeedRequestOptions,
            ChangeFeedStartFrom changeFeedStartFrom)
        {
            if (changeFeedStartFrom == null)
            {
                throw new ArgumentNullException(nameof(changeFeedStartFrom));
            }

            this.documentContainer        = documentContainer ?? throw new ArgumentNullException(nameof(documentContainer));
            this.changeFeedRequestOptions = changeFeedRequestOptions ?? new ChangeFeedRequestOptions();
            this.lazyMonadicEnumerator    = new AsyncLazy <TryCatch <CrossPartitionChangeFeedAsyncEnumerator> >(
                valueFactory: async(cancellationToken) =>
            {
                if (changeFeedStartFrom is ChangeFeedStartFromContinuation startFromContinuation)
                {
                    TryCatch <CosmosElement> monadicParsedToken = CosmosElement.Monadic.Parse(startFromContinuation.Continuation);
                    if (monadicParsedToken.Failed)
                    {
                        return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                   new MalformedChangeFeedContinuationTokenException(
                                       message: $"Failed to parse continuation token: {startFromContinuation.Continuation}.",
                                       innerException: monadicParsedToken.Exception)));
                    }

                    TryCatch <VersionedAndRidCheckedCompositeToken> monadicVersionedToken = VersionedAndRidCheckedCompositeToken
                                                                                            .MonadicCreateFromCosmosElement(monadicParsedToken.Result);
                    if (monadicVersionedToken.Failed)
                    {
                        return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                   new MalformedChangeFeedContinuationTokenException(
                                       message: $"Failed to parse continuation token: {startFromContinuation.Continuation}.",
                                       innerException: monadicVersionedToken.Exception)));
                    }

                    VersionedAndRidCheckedCompositeToken versionedAndRidCheckedCompositeToken = monadicVersionedToken.Result;
                    if (versionedAndRidCheckedCompositeToken.VersionNumber == VersionedAndRidCheckedCompositeToken.Version.V1)
                    {
                        // Need to migrate continuation token
                        if (!(versionedAndRidCheckedCompositeToken.ContinuationToken is CosmosArray cosmosArray))
                        {
                            return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                       new MalformedChangeFeedContinuationTokenException(
                                           message: $"Failed to parse get array continuation token: {startFromContinuation.Continuation}.")));
                        }

                        List <CosmosElement> changeFeedTokensV2 = new List <CosmosElement>();
                        foreach (CosmosElement arrayItem in cosmosArray)
                        {
                            if (!(arrayItem is CosmosObject cosmosObject))
                            {
                                return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                           new MalformedChangeFeedContinuationTokenException(
                                               message: $"Failed to parse get object in composite continuation: {startFromContinuation.Continuation}.")));
                            }

                            if (!cosmosObject.TryGetValue("min", out CosmosString min))
                            {
                                return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                           new MalformedChangeFeedContinuationTokenException(
                                               message: $"Failed to parse start of range: {cosmosObject}.")));
                            }

                            if (!cosmosObject.TryGetValue("max", out CosmosString max))
                            {
                                return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                           new MalformedChangeFeedContinuationTokenException(
                                               message: $"Failed to parse end of range: {cosmosObject}.")));
                            }

                            if (!cosmosObject.TryGetValue("token", out CosmosElement token))
                            {
                                return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                           new MalformedChangeFeedContinuationTokenException(
                                               message: $"Failed to parse token: {cosmosObject}.")));
                            }

                            FeedRangeEpk feedRangeEpk = new FeedRangeEpk(new Documents.Routing.Range <string>(
                                                                             min: min.Value,
                                                                             max: max.Value,
                                                                             isMinInclusive: true,
                                                                             isMaxInclusive: false));
                            ChangeFeedState state = token is CosmosNull ? ChangeFeedState.Beginning() : ChangeFeedStateContinuation.Continuation(token);

                            FeedRangeState <ChangeFeedState> feedRangeState = new FeedRangeState <ChangeFeedState>(feedRangeEpk, state);
                            changeFeedTokensV2.Add(ChangeFeedFeedRangeStateSerializer.ToCosmosElement(feedRangeState));
                        }

                        CosmosArray changeFeedTokensArrayV2 = CosmosArray.Create(changeFeedTokensV2);

                        versionedAndRidCheckedCompositeToken = new VersionedAndRidCheckedCompositeToken(
                            VersionedAndRidCheckedCompositeToken.Version.V2,
                            changeFeedTokensArrayV2,
                            versionedAndRidCheckedCompositeToken.Rid);
                    }

                    if (versionedAndRidCheckedCompositeToken.VersionNumber != VersionedAndRidCheckedCompositeToken.Version.V2)
                    {
                        return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                   new MalformedChangeFeedContinuationTokenException(
                                       message: $"Wrong version number: {versionedAndRidCheckedCompositeToken.VersionNumber}.")));
                    }

                    string collectionRid = await documentContainer.GetResourceIdentifierAsync(cancellationToken);
                    if (versionedAndRidCheckedCompositeToken.Rid != collectionRid)
                    {
                        return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                                   new MalformedChangeFeedContinuationTokenException(
                                       message: $"rids mismatched. Expected: {collectionRid} but got {versionedAndRidCheckedCompositeToken.Rid}.")));
                    }

                    changeFeedStartFrom = ChangeFeedStartFrom.ContinuationToken(versionedAndRidCheckedCompositeToken.ContinuationToken.ToString());
                }

                TryCatch <ChangeFeedCrossFeedRangeState> monadicChangeFeedCrossFeedRangeState = changeFeedStartFrom.Accept(ChangeFeedStateFromToChangeFeedCrossFeedRangeState.Singleton);
                if (monadicChangeFeedCrossFeedRangeState.Failed)
                {
                    return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException(
                               new MalformedChangeFeedContinuationTokenException(
                                   message: $"Could not convert to {nameof(ChangeFeedCrossFeedRangeState)}.",
                                   innerException: monadicChangeFeedCrossFeedRangeState.Exception)));
                }

                CrossPartitionChangeFeedAsyncEnumerator enumerator = CrossPartitionChangeFeedAsyncEnumerator.Create(
                    documentContainer,
                    changeFeedRequestOptions,
                    new CrossFeedRangeState <ChangeFeedState>(monadicChangeFeedCrossFeedRangeState.Result.FeedRangeStates),
                    cancellationToken: default);

                TryCatch <CrossPartitionChangeFeedAsyncEnumerator> monadicEnumerator = TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromResult(enumerator);
                return(monadicEnumerator);
            });
            this.hasMoreResults = true;
        }
Beispiel #17
0
        public async Task ChangeFeed_FeedRange_FromV0Token()
        {
            ContainerResponse largerContainer = await this.database.CreateContainerAsync(
                new ContainerProperties(id : Guid.NewGuid().ToString(), partitionKeyPath : "/status"),
                throughput : 20000,
                cancellationToken : this.cancellationToken);

            ContainerInternal container = (ContainerInlineCore)largerContainer;

            int expected = 100;
            int count    = 0;

            await this.CreateRandomItems((ContainerCore)container, expected, randomPartitionKey : true);

            IReadOnlyList <FeedRange> feedRanges = await container.GetFeedRangesAsync();

            List <string> continuations = new List <string>();

            // First do one request to construct the old model information based on Etag
            foreach (FeedRange feedRange in feedRanges)
            {
                IEnumerable <string> pkRangeIds = await container.GetPartitionKeyRangesAsync(feedRange);

                ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions()
                {
                    PageSizeHint = 1
                };
                ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(
                    changeFeedStartFrom: ChangeFeedStartFrom.Beginning(feedRange),
                    changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore;
                ResponseMessage firstResponse = await feedIterator.ReadNextAsync();

                FeedRangeEpk FeedRangeEpk = feedRange as FeedRangeEpk;

                // Construct the continuation's range, using PKRangeId + ETag
                List <dynamic> ct = new List <dynamic>()
                {
                    new
                    {
                        min   = FeedRangeEpk.Range.Min,
                        max   = FeedRangeEpk.Range.Max,
                        token = (string)null
                    }
                };

                // Extract Etag and manually construct the continuation
                dynamic oldContinuation = new
                {
                    V            = 0,
                    Rid          = await container.GetCachedRIDAsync(cancellationToken : this.cancellationToken),
                    Continuation = ct
                };
                continuations.Add(JsonConvert.SerializeObject(oldContinuation));
            }

            // Now start the new iterators with the constructed continuations from migration
            foreach (string continuation in continuations)
            {
                ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions()
                {
                    PageSizeHint             = 100,
                    EmitOldContinuationToken = true,
                };
                ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(
                    changeFeedStartFrom: ChangeFeedStartFrom.ContinuationToken(continuation),
                    changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore;
                ResponseMessage firstResponse = await feedIterator.ReadNextAsync();

                if (firstResponse.IsSuccessStatusCode)
                {
                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(firstResponse.Content).Data;
                    count += response.Count;
                    string migratedContinuation = firstResponse.ContinuationToken;
                    Assert.IsTrue(FeedRangeContinuation.TryParse(migratedContinuation, out FeedRangeContinuation feedRangeContinuation));
                    Assert.IsTrue(feedRangeContinuation.FeedRange is FeedRangeEpk);
                }
            }

            Assert.AreEqual(expected, count);
        }
Beispiel #18
0
        public async Task StandByFeedIterator()
        {
            int    totalCount       = 0;
            string lastcontinuation = string.Empty;
            int    firstRunTotal    = 25;
            int    batchSize        = 25;

            int pkRangesCount = (await this.Container.ClientContext.DocumentClient.ReadPartitionKeyRangeFeedAsync(this.Container.LinkUri)).Count;

            await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey : true);

            ContainerCore itemsCore    = (ContainerCore)this.Container;
            FeedIterator  feedIterator = itemsCore.GetStandByFeedIterator(
                ChangeFeedStartFrom.Beginning());

            while (feedIterator.HasMoreResults)
            {
                using (ResponseMessage responseMessage =
                           await feedIterator.ReadNextAsync(this.cancellationToken))
                {
                    lastcontinuation = responseMessage.Headers.ContinuationToken;
                    Assert.AreEqual(responseMessage.ContinuationToken, responseMessage.Headers.ContinuationToken);
                    List <CompositeContinuationToken> deserializedToken = JsonConvert.DeserializeObject <List <CompositeContinuationToken> >(lastcontinuation);
                    Assert.AreEqual(pkRangesCount, deserializedToken.Count);
                    if (!responseMessage.IsSuccessStatusCode)
                    {
                        break;
                    }

                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                    totalCount += response.Count;
                }
            }
            Assert.AreEqual(firstRunTotal, totalCount);

            int expectedFinalCount = 50;

            // Insert another batch of 25 and use the last continuation token from the first cycle
            await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey : true);

            FeedIterator setIteratorNew =
                itemsCore.GetStandByFeedIterator(
                    ChangeFeedStartFrom.ContinuationToken(lastcontinuation));

            while (setIteratorNew.HasMoreResults)
            {
                using (ResponseMessage responseMessage =
                           await setIteratorNew.ReadNextAsync(this.cancellationToken))
                {
                    lastcontinuation = responseMessage.Headers.ContinuationToken;
                    Assert.AreEqual(responseMessage.ContinuationToken, responseMessage.Headers.ContinuationToken);
                    if (!responseMessage.IsSuccessStatusCode)
                    {
                        break;
                    }

                    Collection <ToDoActivity> response = TestCommon.SerializerCore.FromStream <CosmosFeedResponseUtil <ToDoActivity> >(responseMessage.Content).Data;
                    totalCount += response.Count;
                }
            }

            Assert.AreEqual(expectedFinalCount, totalCount);
        }
Beispiel #19
0
        public async Task ChangeFeedIteratorCore_WithFullFidelity()
        {
            ContainerProperties properties = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: ChangeFeedIteratorCoreTests.PartitionKey);

            properties.ChangeFeedPolicy.FullFidelityRetention = TimeSpan.FromMinutes(5);
            ContainerResponse response = await this.database.CreateContainerAsync(
                properties,
                cancellationToken : this.cancellationToken);

            ContainerInternal container = (ContainerInternal)response;
            // FF does not work with StartFromBeginning currently, so we capture an initial continuation.
            FeedIterator <ToDoActivityWithMetadata> fullFidelityIterator = container.GetChangeFeedIterator <ToDoActivityWithMetadata>(
                ChangeFeedStartFrom.Now(),
                ChangeFeedMode.FullFidelity);
            string initialContinuation = null;

            while (fullFidelityIterator.HasMoreResults)
            {
                try
                {
                    FeedResponse <ToDoActivityWithMetadata> feedResponse = await fullFidelityIterator.ReadNextAsync(this.cancellationToken);

                    initialContinuation = feedResponse.ContinuationToken;
                }
                catch (CosmosException cosmosException) when(cosmosException.StatusCode == HttpStatusCode.NotModified)
                {
                    initialContinuation = cosmosException.Headers.ContinuationToken;
                    break;
                }
            }

            // Insert documents and then delete them
            int totalDocuments = 50;
            IList <ToDoActivity> createdItems = await this.CreateRandomItems(container, totalDocuments, randomPartitionKey : true);

            foreach (ToDoActivity item in createdItems)
            {
                await container.DeleteItemAsync <ToDoActivity>(item.id, new PartitionKey(item.pk));
            }

            // Resume Change Feed and verify we pickup all the events
            fullFidelityIterator = container.GetChangeFeedIterator <ToDoActivityWithMetadata>(
                ChangeFeedStartFrom.ContinuationToken(initialContinuation),
                ChangeFeedMode.FullFidelity);
            int  detectedEvents = 0;
            bool hasInserts     = false;
            bool hasDeletes     = false;

            while (fullFidelityIterator.HasMoreResults)
            {
                try
                {
                    FeedResponse <ToDoActivityWithMetadata> feedResponse = await fullFidelityIterator.ReadNextAsync(this.cancellationToken);

                    foreach (ToDoActivityWithMetadata item in feedResponse)
                    {
                        Assert.IsNotNull(item.metadata, "Metadata not present");
                        Assert.IsNotNull(item.metadata.operationType, "Metadata has no operationType");
                        hasInserts |= item.metadata.operationType == "create";
                        hasDeletes |= item.metadata.operationType == "delete";
                    }

                    detectedEvents += feedResponse.Count;
                }
                catch (CosmosException cosmosException) when(cosmosException.StatusCode == HttpStatusCode.NotModified)
                {
                    break;
                }
            }

            Assert.AreEqual(2 * totalDocuments, detectedEvents, "Full Fidelity should include inserts and delete events.");
            Assert.IsTrue(hasInserts, "No metadata for create operationType found");
            Assert.IsTrue(hasDeletes, "No metadata for delete operationType found");
        }
        public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll()
        {
            int totalCount    = 0;
            int firstRunTotal = 25;
            int batchSize     = 25;

            string pkToRead = "pkToRead";
            string otherPK  = "otherPK";

            for (int i = 0; i < batchSize; i++)
            {
                await this.Container.CreateItemAsync(this.CreateRandomToDoActivity(pkToRead));
            }

            for (int i = 0; i < batchSize; i++)
            {
                await this.Container.CreateItemAsync(this.CreateRandomToDoActivity(otherPK));
            }

            ContainerInternal           itemsCore    = this.Container;
            FeedIterator <ToDoActivity> feedIterator = itemsCore.GetChangeFeedIterator <ToDoActivity>(
                ChangeFeedStartFrom.Beginning(
                    new FeedRangePartitionKey(
                        new PartitionKey(pkToRead))),
                new ChangeFeedRequestOptions()
            {
                PageSizeHint = 1,
            });
            string continuation = null;

            while (feedIterator.HasMoreResults)
            {
                FeedResponse <ToDoActivity> feedResponse = await feedIterator.ReadNextAsync(this.cancellationToken);

                totalCount += feedResponse.Count;
                foreach (ToDoActivity toDoActivity in feedResponse)
                {
                    Assert.AreEqual(pkToRead, toDoActivity.status);
                }

                continuation = feedResponse.ContinuationToken;
            }

            Assert.AreEqual(firstRunTotal, totalCount);

            int expectedFinalCount = 50;

            // Insert another batch of 25 and use the last FeedToken from the first cycle
            for (int i = 0; i < batchSize; i++)
            {
                await this.Container.CreateItemAsync(this.CreateRandomToDoActivity(pkToRead));
            }

            FeedIterator <ToDoActivity> setIteratorNew = itemsCore.GetChangeFeedIterator <ToDoActivity>(
                ChangeFeedStartFrom.ContinuationToken(continuation));

            while (setIteratorNew.HasMoreResults)
            {
                FeedResponse <ToDoActivity> feedResponse = await setIteratorNew.ReadNextAsync(this.cancellationToken);

                totalCount += feedResponse.Count;
                foreach (ToDoActivity toDoActivity in feedResponse)
                {
                    Assert.AreEqual(pkToRead, toDoActivity.status);
                }
            }

            Assert.AreEqual(expectedFinalCount, totalCount);
        }