private async Task ProcessHiveAsync(
            HiveType hive,
            string id,
            IReadOnlyList <CatalogCommitItem> entries,
            IReadOnlyDictionary <CatalogCommitItem, PackageDetailsCatalogLeaf> entryToLeaf,
            DateTimeOffset registrationCommitTimestamp)
        {
            var registrationCommitId = Guid.NewGuid().ToString();
            var registrationCommit   = new CatalogCommit(registrationCommitId, registrationCommitTimestamp);

            using (_logger.BeginScope(
                       "Processing package {PackageId}, " +
                       "hive {Hive}, " +
                       "replica hives {ReplicaHives}, " +
                       "registration commit ID {RegistrationCommitId}, " +
                       "registration commit timestamp {RegistrationCommitTimestamp:O}.",
                       id,
                       hive,
                       HiveToReplicaHives[hive],
                       registrationCommitId,
                       registrationCommitTimestamp))
            {
                _logger.LogInformation("Processing {Count} catalog commit items.", entries.Count);
                await _hiveUpdater.UpdateAsync(hive, HiveToReplicaHives[hive], id, entries, entryToLeaf, registrationCommit);
            }
        }
Example #2
0
            public Facts()
            {
                Options = new Mock <IOptionsSnapshot <Catalog2RegistrationConfiguration> >();
                Config  = new Catalog2RegistrationConfiguration
                {
                    LegacyBaseUrl        = "https://example/reg/",
                    GzippedBaseUrl       = "https://example/reg-gz/",
                    SemVer2BaseUrl       = "https://example/reg-gz-semver2/",
                    GalleryBaseUrl       = "https://example-gallery/",
                    FlatContainerBaseUrl = "https://example/fc/",
                };
                Options.Setup(x => x.Value).Returns(() => Config);

                LeafItem       = new RegistrationLeafItem();
                Page           = new RegistrationPage();
                Index          = new RegistrationIndex();
                Hive           = HiveType.Legacy;
                Id             = V3Data.PackageId;
                PackageDetails = new PackageDetailsCatalogLeaf
                {
                    PackageVersion = V3Data.NormalizedVersion,
                };
                Commit = new CatalogCommit(V3Data.CommitId, V3Data.CommitTimestamp);

                SerializerSettings            = NuGetJsonSerialization.Settings;
                SerializerSettings.Formatting = Formatting.Indented;
            }
Example #3
0
        private async Task UpdateInlinedPagesAsync(
            HiveType hive,
            string id,
            IndexInfo indexInfo,
            CatalogCommit registrationCommit)
        {
            for (var pageIndex = 0; pageIndex < indexInfo.Items.Count; pageIndex++)
            {
                var pageInfo = indexInfo.Items[pageIndex];

                if (!pageInfo.IsInlined)
                {
                    _logger.LogInformation(
                        "Moving page {PageNumber}/{PageCount} [{Lower}, {Upper}] from having its own blob to being inlined.",
                        pageIndex + 1,
                        indexInfo.Items.Count,
                        pageInfo.Lower.ToNormalizedString(),
                        pageInfo.Upper.ToNormalizedString());

                    pageInfo = await pageInfo.CloneToInlinedAsync();

                    indexInfo.RemoveAt(pageIndex);
                    indexInfo.Insert(pageIndex, pageInfo);
                }

                Guard.Assert(pageInfo.IsInlined, "The page should be inlined at this point.");

                _entityBuilder.UpdateInlinedPageItem(pageInfo.PageItem, hive, id, pageInfo.Count, pageInfo.Lower, pageInfo.Upper);
                _entityBuilder.UpdateCommit(pageInfo.PageItem, registrationCommit);
            }
        }
Example #4
0
        private async Task UpdateLeavesAsync(
            HiveType hive,
            IReadOnlyList <HiveType> replicaHives,
            string id,
            Dictionary <NuGetVersion, PackageDetailsCatalogLeaf> versionToCatalogLeaf,
            CatalogCommit registrationCommit,
            HiveMergeResult mergeResult)
        {
            if (!mergeResult.ModifiedLeaves.Any())
            {
                _logger.LogInformation("No leaves need to be updated.");
                return;
            }

            _logger.LogInformation(
                "Updating {Count} registration leaves.",
                mergeResult.ModifiedLeaves.Count,
                id,
                hive);

            var taskFactories = new ConcurrentBag <Func <Task> >();

            foreach (var leafInfo in mergeResult.ModifiedLeaves)
            {
                _entityBuilder.UpdateLeafItem(leafInfo.LeafItem, hive, id, versionToCatalogLeaf[leafInfo.Version]);
                _entityBuilder.UpdateCommit(leafInfo.LeafItem, registrationCommit);
                var leaf = _entityBuilder.NewLeaf(leafInfo.LeafItem);
                taskFactories.Add(async() =>
                {
                    _logger.LogInformation("Updating leaf {PackageId} {Version}.", id, leafInfo.Version.ToNormalizedString());
                    await _storage.WriteLeafAsync(hive, replicaHives, id, leafInfo.Version, leaf);
                });
            }

            await ParallelAsync.Repeat(
                async() =>
            {
                await Task.Yield();
                while (taskFactories.TryTake(out var taskFactory))
                {
                    await taskFactory();
                }
            },
                _options.Value.MaxConcurrentOperationsPerHive);
        }
Example #5
0
 public void UpdateCommit(ICommitted committed, CatalogCommit commit)
 {
     committed.CommitId        = commit.Id;
     committed.CommitTimestamp = commit.Timestamp;
 }
Example #6
0
        public async Task UpdateAsync(
            HiveType hive,
            IReadOnlyList <HiveType> replicaHives,
            string id,
            IReadOnlyList <CatalogCommitItem> entries,
            IReadOnlyDictionary <CatalogCommitItem, PackageDetailsCatalogLeaf> entryToCatalogLeaf,
            CatalogCommit registrationCommit)
        {
            // Validate the input and put it in more convenient forms.
            if (!entries.Any())
            {
                return;
            }
            GuardInput(entries, entryToCatalogLeaf);
            var sortedCatalog        = entries.OrderBy(x => x.PackageIdentity.Version).ToList();
            var versionToCatalogLeaf = entryToCatalogLeaf.ToDictionary(x => x.Key.PackageIdentity.Version, x => x.Value);

            // Remove SemVer 2.0.0 versions if this hive should only have SemVer 1.0.0 versions.
            if (ShouldExcludeSemVer2(hive))
            {
                Guard.Assert(
                    replicaHives.All(ShouldExcludeSemVer2),
                    "A replica hive of a non-SemVer 2.0.0 hive must also exclude SemVer 2.0.0.");

                ExcludeSemVer2(hive, sortedCatalog, versionToCatalogLeaf);
            }
            else
            {
                Guard.Assert(
                    replicaHives.All(x => !ShouldExcludeSemVer2(x)),
                    "A replica hive of a SemVer 2.0.0 hive must also include SemVer 2.0.0.");
            }

            _logger.LogInformation(
                "Starting to update the {PackageId} registration index in the {Hive} hive and {ReplicaHives} replica hives with {UpsertCount} " +
                "package details and {DeleteCount} package deletes.",
                id,
                hive,
                replicaHives,
                entryToCatalogLeaf.Count,
                entries.Count - entryToCatalogLeaf.Count);

            // Read the existing registration index if it exists. If it does not exist, initialize a new index.
            var index = await _storage.ReadIndexOrNullAsync(hive, id);

            IndexInfo indexInfo;

            if (index == null)
            {
                indexInfo = IndexInfo.New();
            }
            else
            {
                indexInfo = IndexInfo.Existing(_storage, hive, index);
            }

            // Find all of the existing page URLs. This will be used later to find orphan pages.
            var existingPageUrls = GetPageUrls(indexInfo);

            // Read all of the obviously relevant pages in parallel. This simply evaluates some work that would
            // otherwise be done lazily.
            await LoadRelevantPagesAsync(sortedCatalog, indexInfo);

            // Merge the incoming catalog entries in memory.
            var mergeResult = await _merger.MergeAsync(indexInfo, sortedCatalog);

            // Write the modified leaves.
            await UpdateLeavesAsync(hive, replicaHives, id, versionToCatalogLeaf, registrationCommit, mergeResult);

            // Write the pages and handle the inline vs. non-inlined cases.
            if (indexInfo.Items.Count == 0)
            {
                _logger.LogInformation("There are no pages to update.");
            }
            else
            {
                var itemCount = indexInfo.Items.Sum(x => x.Count);
                if (itemCount <= _options.Value.MaxInlinedLeafItems)
                {
                    _logger.LogInformation(
                        "There are {Count} total leaf items so the leaf items will be inlined.",
                        itemCount);

                    await UpdateInlinedPagesAsync(hive, id, indexInfo, registrationCommit);
                }
                else
                {
                    _logger.LogInformation(
                        "There are {Count} total leaf items so the leaf items will not be inlined.",
                        itemCount);

                    await UpdateNonInlinedPagesAsync(hive, replicaHives, id, indexInfo, registrationCommit, mergeResult);
                }
            }

            // Write the index, if there were any changes.
            if (mergeResult.ModifiedPages.Any() || mergeResult.ModifiedLeaves.Any())
            {
                _logger.LogInformation("Updating the index.");
                _entityBuilder.UpdateIndex(indexInfo.Index, hive, id, indexInfo.Items.Count);
                _entityBuilder.UpdateCommit(indexInfo.Index, registrationCommit);
                await _storage.WriteIndexAsync(hive, replicaHives, id, indexInfo.Index);
            }

            if (!indexInfo.Items.Any())
            {
                _logger.LogInformation("Deleting the index since there are no more page items.");
                await _storage.DeleteIndexAsync(hive, replicaHives, id);
            }

            // Delete orphan blobs.
            await DeleteOrphansAsync(hive, replicaHives, existingPageUrls, indexInfo, mergeResult);

            _logger.LogInformation(
                "Done updating the {PackageId} registration index in the {Hive} hive and replica hives {ReplicaHives}. {ModifiedPages} pages were " +
                "updated, {ModifiedLeaves} leaves were upserted, and {DeletedLeaves} leaves were deleted.",
                id,
                hive,
                replicaHives,
                mergeResult.ModifiedPages.Count,
                mergeResult.ModifiedLeaves.Count,
                mergeResult.DeletedLeaves.Count);
        }
Example #7
0
        private async Task UpdateNonInlinedPagesAsync(
            HiveType hive,
            IReadOnlyList <HiveType> replicaHives,
            string id,
            IndexInfo indexInfo,
            CatalogCommit registrationCommit,
            HiveMergeResult mergeResult)
        {
            var taskFactories = new ConcurrentBag <Func <Task> >();

            for (var pageIndex = 0; pageIndex < indexInfo.Items.Count; pageIndex++)
            {
                var pageInfo = indexInfo.Items[pageIndex];

                if (pageInfo.IsInlined)
                {
                    _logger.LogInformation(
                        "Moving page {PageNumber}/{PageCount} [{Lower}, {Upper}] from being inlined to having its own blob.",
                        pageIndex + 1,
                        indexInfo.Items.Count,
                        pageInfo.Lower.ToNormalizedString(),
                        pageInfo.Upper.ToNormalizedString());

                    pageInfo = await pageInfo.CloneToNonInlinedAsync();

                    indexInfo.RemoveAt(pageIndex);
                    indexInfo.Insert(pageIndex, pageInfo);
                }
                else if (!mergeResult.ModifiedPages.Contains(pageInfo))
                {
                    _logger.LogInformation(
                        "Skipping unmodified page {PageNumber}/{PageCount} [{Lower}, {Upper}].",
                        pageIndex + 1,
                        indexInfo.Items.Count,
                        pageInfo.Lower.ToNormalizedString(),
                        pageInfo.Upper.ToNormalizedString());

                    continue;
                }

                Guard.Assert(!pageInfo.IsInlined, "The page should not be inlined at this point.");

                var page = await pageInfo.GetPageAsync();

                _entityBuilder.UpdateNonInlinedPageItem(pageInfo.PageItem, hive, id, pageInfo.Count, pageInfo.Lower, pageInfo.Upper);
                _entityBuilder.UpdateCommit(pageInfo.PageItem, registrationCommit);
                _entityBuilder.UpdatePage(page, hive, id, pageInfo.Count, pageInfo.Lower, pageInfo.Upper);
                _entityBuilder.UpdateCommit(page, registrationCommit);

                var pageNumber = pageIndex + 1;
                taskFactories.Add(async() =>
                {
                    _logger.LogInformation(
                        "Updating page {PageNumber}/{PageCount} [{Lower}, {Upper}].",
                        pageNumber,
                        indexInfo.Items.Count,
                        pageInfo.Lower.ToNormalizedString(),
                        pageInfo.Upper.ToNormalizedString());
                    await _storage.WritePageAsync(hive, replicaHives, id, pageInfo.Lower, pageInfo.Upper, page);
                });
            }

            await ParallelAsync.Repeat(
                async() =>
            {
                await Task.Yield();
                while (taskFactories.TryTake(out var taskFactory))
                {
                    await taskFactory();
                }
            },
                _options.Value.MaxConcurrentOperationsPerHive);
        }
Example #8
0
            public Facts(ITestOutputHelper output)
            {
                Storage       = new Mock <IHiveStorage>();
                Merger        = new Mock <IHiveMerger>();
                EntityBuilder = new Mock <IEntityBuilder>();
                Options       = new Mock <IOptionsSnapshot <Catalog2RegistrationConfiguration> >();
                Logger        = output.GetLogger <HiveUpdater>();

                Config             = new Catalog2RegistrationConfiguration();
                Hive               = HiveType.SemVer2;
                ReplicaHives       = new List <HiveType>();
                Id                 = "NuGet.Versioning";
                Entries            = new List <CatalogCommitItem>();
                EntryToCatalogLeaf = new Dictionary <CatalogCommitItem, PackageDetailsCatalogLeaf>(
                    ReferenceEqualityComparer <CatalogCommitItem> .Default);
                RegistrationIndex = new RegistrationIndex
                {
                    Items = new List <RegistrationPage>
                    {
                        new RegistrationPage
                        {
                            Lower = "1.0.0",
                            Upper = "3.0.0",
                            Count = 2,
                            Items = new List <RegistrationLeafItem>
                            {
                                new RegistrationLeafItem
                                {
                                    Url          = $"https://example/reg/{Id.ToLowerInvariant()}/1.0.0.json",
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Version = "1.0.0",
                                    }
                                },
                                new RegistrationLeafItem
                                {
                                    Url          = $"https://example/reg/{Id.ToLowerInvariant()}/3.0.0.json",
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Version = "3.0.0",
                                    }
                                },
                            }
                        }
                    }
                };
                MergeResult = new HiveMergeResult(
                    new HashSet <PageInfo>(),
                    new HashSet <LeafInfo>(),
                    new HashSet <LeafInfo>());
                RegistrationLeaf   = new RegistrationLeaf();
                RegistrationCommit = new CatalogCommit(
                    "b580f835-f041-4361-aa46-57e5dc338a63",
                    new DateTimeOffset(2019, 10, 25, 0, 0, 0, TimeSpan.Zero));

                Options.Setup(x => x.Value).Returns(() => Config);
                Storage
                .Setup(x => x.ReadIndexOrNullAsync(It.IsAny <HiveType>(), It.IsAny <string>()))
                .ReturnsAsync(() => RegistrationIndex);
                var concreteHiveMerger = new HiveMerger(Options.Object, output.GetLogger <HiveMerger>());

                Merger
                .Setup(x => x.MergeAsync(It.IsAny <IndexInfo>(), It.IsAny <IReadOnlyList <CatalogCommitItem> >()))
                .Returns <IndexInfo, IReadOnlyList <CatalogCommitItem> >((i, e) => concreteHiveMerger.MergeAsync(i, e));
                EntityBuilder
                .Setup(x => x.NewLeaf(It.IsAny <RegistrationLeafItem>()))
                .Returns(() => RegistrationLeaf);
                EntityBuilder
                .Setup(x => x.UpdateNonInlinedPageItem(
                           It.IsAny <RegistrationPage>(),
                           It.IsAny <HiveType>(),
                           It.IsAny <string>(),
                           It.IsAny <int>(),
                           It.IsAny <NuGetVersion>(),
                           It.IsAny <NuGetVersion>()))
                .Callback <RegistrationPage, HiveType, string, int, NuGetVersion, NuGetVersion>((p, h, id, c, l, u) =>
                {
                    p.Url = $"https://example/reg/" +
                            $"{id.ToLowerInvariant()}/" +
                            $"{l.ToNormalizedString().ToLowerInvariant()}/" +
                            $"{u.ToNormalizedString().ToLowerInvariant()}.json";
                });

                Target = new HiveUpdater(
                    Storage.Object,
                    Merger.Object,
                    EntityBuilder.Object,
                    Options.Object,
                    Logger);
            }