Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit 10c1074

Browse files
committed
Update the package hash and length in DB after blob copy (#364)
Progress NuGet/Engineering#1190
1 parent 8c28a19 commit 10c1074

20 files changed

Lines changed: 480 additions & 89 deletions

src/NuGet.Services.Validation.Orchestrator/Configuration/ConfigurationValidator.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ private void CheckUnknownValidators()
102102
{
103103
foreach (var validatorItem in _configuration.Validations)
104104
{
105-
// This method will throw if the validator does not exist.
106-
var validatorType = _validatorProvider.GetValidatorType(validatorItem.Name);
107-
if (validatorType == null)
105+
if (!_validatorProvider.IsValidator(validatorItem.Name))
108106
{
109107
throw new ConfigurationErrorsException("Validator implementation not found for " + validatorItem.Name);
110108
}
@@ -142,7 +140,7 @@ private void CheckForCyclesAndParallelProcessors()
142140
var processorNames = _configuration
143141
.Validations
144142
.Select(x => x.Name)
145-
.Where(x => typeof(IProcessor).IsAssignableFrom(_validatorProvider.GetValidatorType(x)))
143+
.Where(x => _validatorProvider.IsProcessor(x))
146144
.ToList();
147145

148146
TopologicalSort.Validate(_configuration.Validations, processorNames);

src/NuGet.Services.Validation.Orchestrator/IValidationPackageFileService.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.IO;
56
using System.Threading.Tasks;
67
using NuGetGallery;
78

89
namespace NuGet.Services.Validation.Orchestrator
910
{
1011
public interface IValidationPackageFileService : ICorePackageFileService
1112
{
13+
/// <summary>
14+
/// Download the package content from the packages container to a temporary location on disk.
15+
/// </summary>
16+
/// <param name="package">The package metadata.</param>
17+
/// <returns>The package stream.</returns>
18+
Task<Stream> DownloadPackageFileToDiskAsync(Package package);
19+
1220
/// <summary>
1321
/// Copy a package from the validation container to a location specific for the validation set. This allows the
1422
/// validation set to have its own copy of the package to mutate (via <see cref="IProcessor"/>) and validate.
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
5-
64
namespace NuGet.Services.Validation.Orchestrator
75
{
86
/// <summary>
97
/// Interface for the class that provides validator instances by their name
108
/// </summary>
119
public interface IValidatorProvider
1210
{
13-
Type GetValidatorType(string validatorName);
11+
bool IsValidator(string validatorName);
12+
bool IsProcessor(string validatorName);
1413
IValidator GetValidator(string validatorName);
1514
}
1615
}

src/NuGet.Services.Validation.Orchestrator/Job.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Linq;
76
using System.Net;
7+
using System.Net.Http;
8+
using System.Reflection;
89
using System.Threading.Tasks;
910
using AnglicanGeek.MarkdownMailer;
1011
using Autofac;
@@ -18,12 +19,12 @@
1819
using Microsoft.WindowsAzure.Storage;
1920
using NuGet.Jobs;
2021
using NuGet.Jobs.Configuration;
22+
using NuGet.Jobs.Validation;
2123
using NuGet.Jobs.Validation.Common;
2224
using NuGet.Jobs.Validation.PackageSigning.Messages;
2325
using NuGet.Jobs.Validation.PackageSigning.Storage;
2426
using NuGet.Services.Configuration;
2527
using NuGet.Services.KeyVault;
26-
using NuGet.Services.Logging;
2728
using NuGet.Services.ServiceBus;
2829
using NuGet.Services.Validation.Orchestrator.Telemetry;
2930
using NuGet.Services.Validation.PackageCertificates;
@@ -48,6 +49,7 @@ public class Job : JobBase
4849
private const string ServiceBusConfigurationSectionName = "ServiceBus";
4950
private const string SmtpConfigurationSectionName = "Smtp";
5051
private const string EmailConfigurationSectionName = "Email";
52+
private const string PackageDownloadTimeoutName = "PackageDownloadTimeout";
5153

5254
private const string VcsBindingKey = VcsSectionName;
5355
private const string PackageVerificationTopicClientBindingKey = "PackageVerificationTopicClient";
@@ -192,7 +194,7 @@ private void ConfigureJobServices(IServiceCollection services, IConfigurationRoo
192194
});
193195
services.AddTransient<NuGetGallery.ICoreFileStorageService, NuGetGallery.CloudBlobCoreFileStorageService>();
194196
services.AddTransient<IValidationPackageFileService, ValidationPackageFileService>();
195-
services.AddTransient<IValidationOutcomeProcessor, ValidationOutcomeProcessor>();
197+
services.AddTransient<IPackageDownloader, PackageDownloader>();
196198
services.AddTransient<IPackageStatusProcessor, PackageStatusProcessor>();
197199
services.AddTransient<IValidationSetProvider, ValidationSetProvider>();
198200
services.AddTransient<IValidationSetProcessor, ValidationSetProcessor>();
@@ -230,8 +232,27 @@ private void ConfigureJobServices(IServiceCollection services, IConfigurationRoo
230232
services.AddTransient<ICoreMessageServiceConfiguration, CoreMessageServiceConfiguration>();
231233
services.AddTransient<ICoreMessageService, CoreMessageService>();
232234
services.AddTransient<IMessageService, MessageService>();
235+
services.AddTransient<ICommonTelemetryService, CommonTelemetryService>();
233236
services.AddTransient<ITelemetryService, TelemetryService>();
234237
services.AddSingleton(new TelemetryClient());
238+
services.AddTransient<IValidationOutcomeProcessor, ValidationOutcomeProcessor>();
239+
services.AddSingleton(p =>
240+
{
241+
var assembly = Assembly.GetEntryAssembly();
242+
var assemblyName = assembly.GetName().Name;
243+
var assemblyVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "0.0.0";
244+
245+
var client = new HttpClient(new WebRequestHandler
246+
{
247+
AllowPipelining = true,
248+
AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate),
249+
});
250+
251+
client.Timeout = configurationRoot.GetValue<TimeSpan>(PackageDownloadTimeoutName);
252+
client.DefaultRequestHeaders.Add("User-Agent", $"{assemblyName}/{assemblyVersion}");
253+
254+
return client;
255+
});
235256
}
236257

237258
private static IServiceProvider CreateProvider(IServiceCollection services)

src/NuGet.Services.Validation.Orchestrator/PackageStatusProcessor.cs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,34 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics;
6+
using System.Linq;
57
using System.Threading.Tasks;
68
using Microsoft.Extensions.Logging;
79
using NuGet.Services.Validation.Orchestrator.Telemetry;
810
using NuGetGallery;
11+
using NuGetGallery.Packaging;
912

1013
namespace NuGet.Services.Validation.Orchestrator
1114
{
1215
public class PackageStatusProcessor : IPackageStatusProcessor
1316
{
1417
private readonly ICorePackageService _galleryPackageService;
1518
private readonly IValidationPackageFileService _packageFileService;
19+
private readonly IValidatorProvider _validatorProvider;
1620
private readonly ITelemetryService _telemetryService;
1721
private readonly ILogger<PackageStatusProcessor> _logger;
1822

1923
public PackageStatusProcessor(
2024
ICorePackageService galleryPackageService,
2125
IValidationPackageFileService packageFileService,
26+
IValidatorProvider validatorProvider,
2227
ITelemetryService telemetryService,
2328
ILogger<PackageStatusProcessor> logger)
2429
{
2530
_galleryPackageService = galleryPackageService ?? throw new ArgumentNullException(nameof(galleryPackageService));
2631
_packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService));
32+
_validatorProvider = validatorProvider ?? throw new ArgumentNullException(nameof(validatorProvider));
2733
_telemetryService = telemetryService ?? throw new ArgumentNullException(nameof(telemetryService));
2834
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
2935
}
@@ -127,8 +133,15 @@ private async Task MoveFileToPublicStorageAndMarkPackageAsAvailable(PackageValid
127133
package.NormalizedVersion,
128134
validationSet.ValidationTrackingId);
129135

136+
// If the validation set contains any processors, we must use the copy of the package that is specific to
137+
// this validation set. We can't use the original validation package because it does not have any of the
138+
// changes that the processors made. If the validation set package does not exist for some reason and there
139+
// are processors in the validation set, this indicates a bug and an exception will be thrown by the copy
140+
// operation below. This will cause the validation queue message to eventually dead-letter at which point
141+
// the on-call person should investigate.
130142
bool copied;
131-
if (await _packageFileService.DoesValidationSetPackageExistAsync(validationSet))
143+
if (validationSet.PackageValidations.Any(x => _validatorProvider.IsProcessor(x.Type)) ||
144+
await _packageFileService.DoesValidationSetPackageExistAsync(validationSet))
132145
{
133146
copied = await CopyAsync(
134147
validationSet,
@@ -150,6 +163,39 @@ private async Task MoveFileToPublicStorageAndMarkPackageAsAvailable(PackageValid
150163
x => _packageFileService.CopyValidationPackageToPackageFileAsync(x.PackageId, x.PackageNormalizedVersion));
151164
}
152165

166+
// Use whatever package made it into the packages container. This is what customers will consume so the DB
167+
// record must match.
168+
using (var packageStream = await _packageFileService.DownloadPackageFileToDiskAsync(package))
169+
{
170+
var stopwatch = Stopwatch.StartNew();
171+
var hash = CryptographyService.GenerateHash(packageStream, CoreConstants.Sha512HashAlgorithmId);
172+
_telemetryService.TrackDurationToHashPackage(
173+
stopwatch.Elapsed,
174+
package.PackageRegistration.Id,
175+
package.NormalizedVersion,
176+
CoreConstants.Sha512HashAlgorithmId,
177+
packageStream.GetType().FullName);
178+
179+
var streamMetadata = new PackageStreamMetadata
180+
{
181+
Size = packageStream.Length,
182+
Hash = hash,
183+
HashAlgorithm = CoreConstants.Sha512HashAlgorithmId,
184+
};
185+
186+
// We don't immediately commit here. Later, we will commit these changes as well as the new package
187+
// status as part of the same transaction.
188+
if (streamMetadata.Size != package.PackageFileSize
189+
|| streamMetadata.Hash != package.Hash
190+
|| streamMetadata.HashAlgorithm != package.HashAlgorithm)
191+
{
192+
await _galleryPackageService.UpdatePackageStreamMetadataAsync(
193+
package,
194+
streamMetadata,
195+
commitChanges: false);
196+
}
197+
}
198+
153199
_logger.LogInformation("Marking package {PackageId} {PackageVersion}, validation set {ValidationSetId} as {PackageStatus} in DB",
154200
package.PackageRegistration.Id,
155201
package.NormalizedVersion,
@@ -158,6 +204,7 @@ private async Task MoveFileToPublicStorageAndMarkPackageAsAvailable(PackageValid
158204

159205
try
160206
{
207+
// Make the package available and commit any other pending changes (e.g. updated hash).
161208
await UpdatePackageStatusAsync(package, PackageStatus.Available);
162209
}
163210
catch (Exception e)
@@ -171,8 +218,9 @@ private async Task MoveFileToPublicStorageAndMarkPackageAsAvailable(PackageValid
171218
validationSet.ValidationTrackingId);
172219

173220
// If this execution was not the one to copy the package, then don't delete the package on failure.
174-
// This prevents the (unlikely) case where two actors attempt the DB update, one suceeds and one fails.
175-
// We don't want an available package record with nothing in the packages container!
221+
// This prevents a missing passing in the (unlikely) case where two actors attempt the DB update, one
222+
// succeeds and one fails. We don't want an available package record with nothing in the packages
223+
// container!
176224
if (copied)
177225
{
178226
await _packageFileService.DeletePackageFileAsync(package.PackageRegistration.Id, package.Version);
@@ -200,9 +248,10 @@ private async Task<bool> CopyAsync(PackageValidationSet validationSet, Package p
200248
catch (InvalidOperationException)
201249
{
202250
// The package already exists in the packages container. This can happen if the DB commit below fails
203-
// and this flow is retried. We assume that the package content has not changed. Today there is no way
204-
// for the content to change. Hard deletes (the one way a package ID and version can get different
205-
// content) delete from both the packages and validating container so this can't be a mismatch.
251+
// and this flow is retried or another validation set for the package completed first. Either way, we
252+
// will later attempt to use the hash from the package in the packages container (the destination).
253+
// In other words, we don't care which copy wins, but the DB record must match the package that ends
254+
// up in the packages container.
206255
_logger.LogInformation(
207256
"Package already exists in packages container for {PackageId} {PackageVersion}, validation set {ValidationSetId}",
208257
package.PackageRegistration.Id,

src/NuGet.Services.Validation.Orchestrator/Telemetry/ITelemetryService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,10 @@ public interface ITelemetryService
9191
/// in the public container.
9292
/// </summary>
9393
void TrackMissingNupkgForAvailablePackage(string packageId, string normalizedVersion, string validationTrackingId);
94+
95+
/// <summary>
96+
/// A metric to of how long it took to hash a package.
97+
/// </summary>
98+
void TrackDurationToHashPackage(TimeSpan duration, string packageId, string normalizedVersion, string hashAlgorithm, string streamType);
9499
}
95100
}

src/NuGet.Services.Validation.Orchestrator/Telemetry/TelemetryService.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class TelemetryService : ITelemetryService
2323
private const string ClientValidationIssue = Prefix + "ClientValidationIssue";
2424
private const string MissingPackageForValidationMessage = Prefix + "MissingPackageForValidationMessage";
2525
private const string MissingNupkgForAvailablePackage = Prefix + "MissingNupkgForAvailablePackage";
26+
private const string DurationToHashPackageSeconds = Prefix + "DurationToHashPackageSeconds";
2627

2728
private const string FromStatus = "FromStatus";
2829
private const string ToStatus = "ToStatus";
@@ -33,6 +34,9 @@ public class TelemetryService : ITelemetryService
3334
private const string PackageId = "PackageId";
3435
private const string NormalizedVersion = "NormalizedVersion";
3536
private const string ValidationTrackingId = "ValidationTrackingId";
37+
private const string PackageSize = "PackageSize";
38+
private const string HashAlgorithm = "HashAlgorithm";
39+
private const string StreamType = "StreamType";
3640

3741
private readonly TelemetryClient _telemetryClient;
3842

@@ -41,6 +45,21 @@ public TelemetryService(TelemetryClient telemetryClient)
4145
_telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient));
4246
}
4347

48+
public void TrackDurationToHashPackage(TimeSpan duration, string packageId, string normalizedVersion, string hashAlgorithm, string streamType)
49+
{
50+
_telemetryClient.TrackMetric(
51+
DurationToHashPackageSeconds,
52+
duration.TotalSeconds,
53+
new Dictionary<string, string>
54+
{
55+
{ PackageId, packageId },
56+
{ NormalizedVersion, normalizedVersion },
57+
{ PackageSize, PackageSize.ToString() },
58+
{ HashAlgorithm, hashAlgorithm },
59+
{ StreamType, streamType },
60+
});
61+
}
62+
4463
public void TrackDurationToValidationSetCreation(TimeSpan duration)
4564
{
4665
_telemetryClient.TrackMetric(

src/NuGet.Services.Validation.Orchestrator/ValidationPackageFileService.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,38 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.IO;
6+
using System.Threading;
57
using System.Threading.Tasks;
68
using Microsoft.Extensions.Logging;
9+
using NuGet.Jobs.Validation;
710
using NuGetGallery;
811

912
namespace NuGet.Services.Validation.Orchestrator
1013
{
1114
public class ValidationPackageFileService : CorePackageFileService, IValidationPackageFileService
1215
{
1316
private readonly ICoreFileStorageService _fileStorageService;
17+
private readonly IPackageDownloader _packageDownloader;
1418
private readonly ILogger<ValidationPackageFileService> _logger;
1519

1620
public ValidationPackageFileService(
1721
ICoreFileStorageService fileStorageService,
22+
IPackageDownloader packageDownloader,
1823
ILogger<ValidationPackageFileService> logger) : base(fileStorageService)
1924
{
2025
_fileStorageService = fileStorageService ?? throw new ArgumentNullException(nameof(fileStorageService));
26+
_packageDownloader = packageDownloader ?? throw new ArgumentNullException(nameof(packageDownloader));
2127
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
2228
}
2329

30+
public async Task<Stream> DownloadPackageFileToDiskAsync(Package package)
31+
{
32+
var packageUri = await GetPackageReadUriAsync(package);
33+
34+
return await _packageDownloader.DownloadAsync(packageUri, CancellationToken.None);
35+
}
36+
2437
public Task CopyValidationPackageForValidationSetAsync(PackageValidationSet validationSet)
2538
{
2639
var srcFileName = BuildFileName(

0 commit comments

Comments
 (0)