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

Commit 255d571

Browse files
committed
Extract gallery DB and packages container update logic to its own class (#362)
Progress on NuGet/Engineering#1190
1 parent 55a05b9 commit 255d571

8 files changed

Lines changed: 718 additions & 492 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Threading.Tasks;
5+
using NuGetGallery;
6+
7+
namespace NuGet.Services.Validation.Orchestrator
8+
{
9+
/// <summary>
10+
/// This interface manages the state of gallery artifacts: gallery DB and packages container.
11+
/// </summary>
12+
public interface IPackageStatusProcessor
13+
{
14+
Task SetPackageStatusAsync(
15+
Package package,
16+
PackageValidationSet validationSet,
17+
PackageStatus packageStatus);
18+
}
19+
}

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

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,19 @@ private void ConfigureJobServices(IServiceCollection services, IConfigurationRoo
183183
services.AddTransient<IPackageCriteriaEvaluator, PackageCriteriaEvaluator>();
184184
services.AddTransient<VcsValidator>();
185185
services.AddTransient<IPackageSignatureVerificationEnqueuer, PackageSignatureVerificationEnqueuer>();
186+
services.AddTransient<NuGetGallery.ICloudBlobClient>(c =>
187+
{
188+
var configurationAccessor = c.GetRequiredService<IOptionsSnapshot<ValidationConfiguration>>();
189+
return new NuGetGallery.CloudBlobClientWrapper(
190+
configurationAccessor.Value.ValidationStorageConnectionString,
191+
readAccessGeoRedundant: false);
192+
});
193+
services.AddTransient<NuGetGallery.ICoreFileStorageService, NuGetGallery.CloudBlobCoreFileStorageService>();
194+
services.AddTransient<IValidationPackageFileService, ValidationPackageFileService>();
195+
services.AddTransient<IValidationOutcomeProcessor, ValidationOutcomeProcessor>();
196+
services.AddTransient<IPackageStatusProcessor, PackageStatusProcessor>();
197+
services.AddTransient<IValidationSetProvider, ValidationSetProvider>();
198+
services.AddTransient<IValidationSetProcessor, ValidationSetProcessor>();
186199
services.AddTransient<IBrokeredMessageSerializer<SignatureValidationMessage>, SignatureValidationMessageSerializer>();
187200
services.AddTransient<IValidatorStateService, ValidatorStateService>();
188201
services.AddTransient<PackageSigningValidator>();
@@ -273,42 +286,6 @@ private static IServiceProvider CreateProvider(IServiceCollection services)
273286
))
274287
.As<IPackageSignatureVerificationEnqueuer>();
275288

276-
containerBuilder
277-
.Register(c =>
278-
{
279-
var configurationAccessor = c.Resolve<IOptionsSnapshot<ValidationConfiguration>>();
280-
return new NuGetGallery.CloudBlobClientWrapper(configurationAccessor.Value.ValidationStorageConnectionString, false);
281-
})
282-
.Keyed<NuGetGallery.ICloudBlobClient>(ValidationStorageBindingKey);
283-
284-
containerBuilder
285-
.RegisterKeyedTypeWithKeyedParameter<NuGetGallery.ICoreFileStorageService, NuGetGallery.CloudBlobCoreFileStorageService, NuGetGallery.ICloudBlobClient>(
286-
typeKey: ValidationStorageBindingKey,
287-
parameterKey: ValidationStorageBindingKey);
288-
289-
containerBuilder
290-
.RegisterKeyedTypeWithKeyedParameter<IValidationPackageFileService, ValidationPackageFileService, NuGetGallery.ICoreFileStorageService>(
291-
typeKey: ValidationStorageBindingKey,
292-
parameterKey: ValidationStorageBindingKey);
293-
294-
containerBuilder
295-
.RegisterTypeWithKeyedParameter<
296-
IValidationOutcomeProcessor,
297-
ValidationOutcomeProcessor,
298-
IValidationPackageFileService>(ValidationStorageBindingKey);
299-
300-
containerBuilder
301-
.RegisterTypeWithKeyedParameter<
302-
IValidationSetProvider,
303-
ValidationSetProvider,
304-
IValidationPackageFileService>(ValidationStorageBindingKey);
305-
306-
containerBuilder
307-
.RegisterTypeWithKeyedParameter<
308-
IValidationSetProcessor,
309-
ValidationSetProcessor,
310-
IValidationPackageFileService>(ValidationStorageBindingKey);
311-
312289
containerBuilder
313290
.RegisterType<ScopedMessageHandler<PackageValidationMessageData>>()
314291
.Keyed<IMessageHandler<PackageValidationMessageData>>(OrchestratorBindingKey);

src/NuGet.Services.Validation.Orchestrator/NuGet.Services.Validation.Orchestrator.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<Compile Include="Configuration\EmailConfiguration.cs" />
5353
<Compile Include="Error.cs" />
5454
<Compile Include="IMessageService.cs" />
55+
<Compile Include="IPackageStatusProcessor.cs" />
5556
<Compile Include="IValidationOutcomeProcessor.cs" />
5657
<Compile Include="IValidationPackageFileService.cs" />
5758
<Compile Include="IValidationSetProcessor.cs" />
@@ -70,6 +71,7 @@
7071
<Compile Include="PackageSigning\PackageSignatureVerificationEnqueuer.cs" />
7172
<Compile Include="PackageSigning\PackageSigningConfiguration.cs" />
7273
<Compile Include="PackageSigning\PackageSigningValidator.cs" />
74+
<Compile Include="PackageStatusProcessor.cs" />
7375
<Compile Include="PackageValidationMessageDataSerializer.cs" />
7476
<Compile Include="Program.cs" />
7577
<Compile Include="Properties\AssemblyInfo.cs" />
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Logging;
7+
using NuGet.Services.Validation.Orchestrator.Telemetry;
8+
using NuGetGallery;
9+
10+
namespace NuGet.Services.Validation.Orchestrator
11+
{
12+
public class PackageStatusProcessor : IPackageStatusProcessor
13+
{
14+
private readonly ICorePackageService _galleryPackageService;
15+
private readonly IValidationPackageFileService _packageFileService;
16+
private readonly ITelemetryService _telemetryService;
17+
private readonly ILogger<PackageStatusProcessor> _logger;
18+
19+
public PackageStatusProcessor(
20+
ICorePackageService galleryPackageService,
21+
IValidationPackageFileService packageFileService,
22+
ITelemetryService telemetryService,
23+
ILogger<PackageStatusProcessor> logger)
24+
{
25+
_galleryPackageService = galleryPackageService ?? throw new ArgumentNullException(nameof(galleryPackageService));
26+
_packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService));
27+
_telemetryService = telemetryService ?? throw new ArgumentNullException(nameof(telemetryService));
28+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
29+
}
30+
31+
public Task SetPackageStatusAsync(
32+
Package package,
33+
PackageValidationSet validationSet,
34+
PackageStatus packageStatus)
35+
{
36+
if (package == null)
37+
{
38+
throw new ArgumentNullException(nameof(package));
39+
}
40+
41+
if (validationSet == null)
42+
{
43+
throw new ArgumentNullException(nameof(validationSet));
44+
}
45+
46+
if (package.PackageStatusKey == PackageStatus.Deleted)
47+
{
48+
throw new ArgumentException(
49+
$"A package in the {nameof(PackageStatus.Deleted)} state cannot be processed.",
50+
nameof(package));
51+
}
52+
53+
if (package.PackageStatusKey == PackageStatus.Available &&
54+
packageStatus == PackageStatus.FailedValidation)
55+
{
56+
throw new ArgumentException(
57+
$"A package cannot transition from {nameof(PackageStatus.Available)} to {nameof(PackageStatus.FailedValidation)}.",
58+
nameof(packageStatus));
59+
}
60+
61+
switch (packageStatus)
62+
{
63+
case PackageStatus.Available:
64+
return MakePackageAvailableAsync(package, validationSet);
65+
case PackageStatus.FailedValidation:
66+
return MakePackageFailedValidationAsync(package, validationSet);
67+
default:
68+
throw new ArgumentException(
69+
$"A package can only transition to the {nameof(PackageStatus.Available)} or " +
70+
$"{nameof(PackageStatus.FailedValidation)} states.", nameof(packageStatus));
71+
}
72+
}
73+
74+
private Task MakePackageFailedValidationAsync(Package package, PackageValidationSet validationSet)
75+
{
76+
return UpdatePackageStatusAsync(package, PackageStatus.FailedValidation);
77+
}
78+
79+
private async Task MakePackageAvailableAsync(Package package, PackageValidationSet validationSet)
80+
{
81+
if (package.PackageStatusKey != PackageStatus.Available)
82+
{
83+
await MoveFileToPublicStorageAndMarkPackageAsAvailable(validationSet, package);
84+
}
85+
else
86+
{
87+
_logger.LogInformation("Package {PackageId} {PackageVersion} {ValidationSetId} was already available, not going to copy data and update DB",
88+
package.PackageRegistration.Id,
89+
package.NormalizedVersion,
90+
validationSet.ValidationTrackingId);
91+
92+
if (!await _packageFileService.DoesPackageFileExistAsync(package))
93+
{
94+
var validationPackageAvailable = await _packageFileService.DoesValidationPackageFileExistAsync(package);
95+
96+
_logger.LogWarning("Package {PackageId} {PackageVersion} is marked as available, but does not exist " +
97+
"in public container. Does package exist in validation container: {ExistsInValidation}",
98+
package.PackageRegistration.Id,
99+
package.NormalizedVersion,
100+
validationPackageAvailable);
101+
102+
// Report missing package, don't try to fix up anything. This shouldn't happen and needs an investigation.
103+
_telemetryService.TrackMissingNupkgForAvailablePackage(
104+
validationSet.PackageId,
105+
validationSet.PackageNormalizedVersion,
106+
validationSet.ValidationTrackingId.ToString());
107+
}
108+
}
109+
}
110+
111+
private async Task UpdatePackageStatusAsync(Package package, PackageStatus toStatus)
112+
{
113+
var fromStatus = package.PackageStatusKey;
114+
115+
await _galleryPackageService.UpdatePackageStatusAsync(package, toStatus, commitChanges: true);
116+
117+
if (fromStatus != toStatus)
118+
{
119+
_telemetryService.TrackPackageStatusChange(fromStatus, toStatus);
120+
}
121+
}
122+
123+
private async Task MoveFileToPublicStorageAndMarkPackageAsAvailable(PackageValidationSet validationSet, Package package)
124+
{
125+
_logger.LogInformation("Copying .nupkg to public storage for package {PackageId} {PackageVersion}, validation set {ValidationSetId}",
126+
package.PackageRegistration.Id,
127+
package.NormalizedVersion,
128+
validationSet.ValidationTrackingId);
129+
130+
bool copied;
131+
if (await _packageFileService.DoesValidationSetPackageExistAsync(validationSet))
132+
{
133+
copied = await CopyAsync(
134+
validationSet,
135+
package,
136+
x => _packageFileService.CopyValidationSetPackageToPackageFileAsync(x));
137+
}
138+
else
139+
{
140+
_logger.LogInformation(
141+
"The package specific to the validation set does not exist. Falling back to the validation " +
142+
"container for package {PackageId} {PackageVersion}, validation set {ValidationSetId}",
143+
package.PackageRegistration.Id,
144+
package.NormalizedVersion,
145+
validationSet.ValidationTrackingId);
146+
147+
copied = await CopyAsync(
148+
validationSet,
149+
package,
150+
x => _packageFileService.CopyValidationPackageToPackageFileAsync(x.PackageId, x.PackageNormalizedVersion));
151+
}
152+
153+
_logger.LogInformation("Marking package {PackageId} {PackageVersion}, validation set {ValidationSetId} as {PackageStatus} in DB",
154+
package.PackageRegistration.Id,
155+
package.NormalizedVersion,
156+
validationSet.ValidationTrackingId,
157+
PackageStatus.Available);
158+
159+
try
160+
{
161+
await UpdatePackageStatusAsync(package, PackageStatus.Available);
162+
}
163+
catch (Exception e)
164+
{
165+
_logger.LogError(
166+
Error.UpdatingPackageDbStatusFailed,
167+
e,
168+
"Failed to update package status in Gallery Db. Package {PackageId} {PackageVersion}, validation set {ValidationSetId}",
169+
package.PackageRegistration.Id,
170+
package.NormalizedVersion,
171+
validationSet.ValidationTrackingId);
172+
173+
// 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!
176+
if (copied)
177+
{
178+
await _packageFileService.DeletePackageFileAsync(package.PackageRegistration.Id, package.Version);
179+
}
180+
181+
throw;
182+
}
183+
184+
_logger.LogInformation("Deleting from the source for package {PackageId} {PackageVersion}, validation set {ValidationSetId}",
185+
package.PackageRegistration.Id,
186+
package.NormalizedVersion,
187+
validationSet.ValidationTrackingId);
188+
189+
await _packageFileService.DeleteValidationPackageFileAsync(package.PackageRegistration.Id, package.Version);
190+
}
191+
192+
private async Task<bool> CopyAsync(PackageValidationSet validationSet, Package package, Func<PackageValidationSet, Task> copyAsync)
193+
{
194+
try
195+
{
196+
await copyAsync(validationSet);
197+
198+
return true;
199+
}
200+
catch (InvalidOperationException)
201+
{
202+
// 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.
206+
_logger.LogInformation(
207+
"Package already exists in packages container for {PackageId} {PackageVersion}, validation set {ValidationSetId}",
208+
package.PackageRegistration.Id,
209+
package.NormalizedVersion,
210+
validationSet.ValidationTrackingId);
211+
212+
return false;
213+
}
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)