public ValidationMessageHandlerLooseFacts() : base(MockBehavior.Loose) { Package = new Package(); MessageData = new PackageValidationMessageData("packageId", "1.2.3", Guid.NewGuid()); ValidationSet = new PackageValidationSet(); CorePackageServiceMock .Setup(ps => ps.FindPackageByIdAndVersionStrict(MessageData.PackageId, MessageData.PackageVersion)) .Returns(Package); ValidationSetProviderMock .Setup(vsp => vsp.GetOrCreateValidationSetAsync(MessageData.ValidationTrackingId, Package)) .ReturnsAsync(ValidationSet); }
private async Task <RevalidationResult> StartRevalidationAsync(PackageRevalidation revalidation) { var message = new PackageValidationMessageData( revalidation.PackageId, revalidation.PackageNormalizedVersion, revalidation.ValidationTrackingId.Value); await _validationEnqueuer.StartValidationAsync(message); await _packageState.MarkPackageRevalidationAsEnqueuedAsync(revalidation); _telemetryService.TrackPackageRevalidationStarted(revalidation.PackageId, revalidation.PackageNormalizedVersion); return(RevalidationResult.RevalidationEnqueued); }
public async Task WaitsForPackageAvailabilityInGalleryDB() { var messageData = new PackageValidationMessageData("packageId", "1.2.3", Guid.NewGuid()); var validationConfiguration = new ValidationConfiguration(); CorePackageServiceMock .Setup(ps => ps.FindPackageByIdAndVersionStrict(messageData.PackageId, messageData.PackageVersion)) .Returns <Package>(null) .Verifiable(); var handler = CreateHandler(); await handler.HandleAsync(messageData); CorePackageServiceMock.Verify(ps => ps.FindPackageByIdAndVersionStrict(messageData.PackageId, messageData.PackageVersion), Times.Once()); }
public async Task <PackageStatus> StartValidationAsync(TPackageEntity package) { if (_appConfiguration.ReadOnlyMode) { throw new ReadOnlyModeException(Strings.CannotEnqueueDueToReadOnly); } ValidatingType validatingType; var entityKey = package.Key == default(int) ? (int?)null : package.Key; if (package is Package) { validatingType = ValidatingType.Package; } else if (package is SymbolPackage) { validatingType = ValidatingType.SymbolPackage; } else { throw new ArgumentException($"Unknown IPackageEntity type: {nameof(package)}"); } var data = new PackageValidationMessageData( package.Id, package.Version, Guid.NewGuid(), validatingType, entityKey: entityKey); var activityName = $"Enqueuing asynchronous package validation: " + $"{data.PackageId} {data.PackageVersion} {data.ValidatingType} ({data.ValidationTrackingId})"; using (_diagnosticsSource.Activity(activityName)) { var postponeProcessingTill = DateTimeOffset.UtcNow + _appConfiguration.AsynchronousPackageValidationDelay; await _validationEnqueuer.StartValidationAsync(data, postponeProcessingTill); } if (_appConfiguration.BlockingAsynchronousPackageValidationEnabled) { return(PackageStatus.Validating); } return(PackageStatus.Available); }
public async Task ReEnqueuesProcessingIfNotAllComplete() { const int postponeMinutes = 1; AddValidation("validation1", ValidationStatus.Incomplete); Configuration.ValidationMessageRecheckPeriod = TimeSpan.FromMinutes(postponeMinutes); Configuration.TimeoutValidationSetAfter = TimeSpan.FromDays(1); PackageValidationMessageData messageData = null; DateTimeOffset postponeTill = DateTimeOffset.MinValue; ValidationEnqueuerMock .Setup(ve => ve.SendMessageAsync(It.IsAny <PackageValidationMessageData>(), It.IsAny <DateTimeOffset>())) .Returns(Task.FromResult(0)) .Callback <PackageValidationMessageData, DateTimeOffset>((pv, pt) => { messageData = pv; postponeTill = pt; }); var processor = CreateProcessor(); var startTime = DateTimeOffset.Now; await processor.ProcessValidationOutcomeAsync(ValidationSet, PackageValidatingEntity, ProcessorStats, ScheduleNextCheck); ValidationStorageServiceMock .Verify(s => s.UpdateValidationSetAsync(ValidationSet), Times.Once); ValidationEnqueuerMock .Verify(ve => ve.SendMessageAsync(It.IsAny <PackageValidationMessageData>(), It.IsAny <DateTimeOffset>()), Times.Once()); PackageStateProcessorMock.Verify( x => x.SetStatusAsync(It.IsAny <PackageValidatingEntity>(), It.IsAny <PackageValidationSet>(), It.IsAny <PackageStatus>()), Times.Never); PackageFileServiceMock.Verify( x => x.DeletePackageForValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Never); Assert.NotNull(messageData); Assert.Equal(PackageValidationMessageType.ProcessValidationSet, messageData.Type); Assert.Equal(ValidationSet.ValidationTrackingId, messageData.ProcessValidationSet.ValidationTrackingId); Assert.Equal(ValidationSet.PackageId, messageData.ProcessValidationSet.PackageId); Assert.Equal(Package.NormalizedVersion, messageData.ProcessValidationSet.PackageVersion); Assert.Equal(ValidationSet.ValidatingType, messageData.ProcessValidationSet.ValidatingType); Assert.Equal(ValidationSet.PackageKey, messageData.ProcessValidationSet.EntityKey); Assert.Equal(postponeMinutes, (postponeTill - startTime).TotalMinutes, 0); Assert.Equal(ValidationSetStatus.InProgress, ValidationSet.ValidationSetStatus); }
public void ProducesExpectedMessageForCheckValidator() { // Arrange var input = PackageValidationMessageData.NewCheckValidator(ValidationId); // Act var output = _target.SerializePackageValidationMessageData(input); // Assert Assert.Contains(SchemaVersionKey, output.Properties.Keys); Assert.Equal(SchemaVersion1, output.Properties[SchemaVersionKey]); Assert.Contains(SchemaName, output.Properties.Keys); Assert.Equal(CheckValidatorType, output.Properties[SchemaName]); var body = output.GetBody(); Assert.Equal(TestData.SerializedCheckValidatorData, body); }
public void ProducesExpectedMessageForSymbols() { // Arrange var input = new PackageValidationMessageData(PackageId, PackageVersion, ValidationTrackingId, ValidatingType.SymbolPackage); // Act var output = _target.SerializePackageValidationMessageData(input); // Assert Assert.Contains(SchemaVersionKey, output.Properties.Keys); Assert.Equal(SchemaVersion1, output.Properties[SchemaVersionKey]); Assert.Contains(SchemaName, output.Properties.Keys); Assert.Equal(PackageValidationMessageDataType, output.Properties[SchemaName]); var body = output.GetBody(); Assert.Equal(TestData.SerializedPackageValidationMessageDataSymbols, body); }
public async Task DropsMessageOnDuplicateValidationRequest() { var messageData = PackageValidationMessageData.NewProcessValidationSet( "packageId", "1.2.3", Guid.NewGuid(), ValidatingType.SymbolPackage, entityKey: null); var validationConfiguration = new ValidationConfiguration(); var symbolPackage = new SymbolPackage() { Package = new Package() }; var symbolPackageValidatingEntity = new SymbolPackageValidatingEntity(symbolPackage); CoreSymbolPackageServiceMock .Setup(ps => ps.FindPackageByIdAndVersionStrict( messageData.ProcessValidationSet.PackageId, messageData.ProcessValidationSet.PackageVersion)) .Returns(symbolPackageValidatingEntity) .Verifiable(); ValidationSetProviderMock .Setup(vsp => vsp.TryGetOrCreateValidationSetAsync(messageData.ProcessValidationSet, symbolPackageValidatingEntity)) .ReturnsAsync((PackageValidationSet)null) .Verifiable(); var handler = CreateHandler(); var result = await handler.HandleAsync(messageData); Assert.True(result); CoreSymbolPackageServiceMock.Verify( ps => ps.FindPackageByIdAndVersionStrict( messageData.ProcessValidationSet.PackageId, messageData.ProcessValidationSet.PackageVersion), Times.Once); ValidationSetProviderMock.Verify( vsp => vsp.TryGetOrCreateValidationSetAsync( messageData.ProcessValidationSet, symbolPackageValidatingEntity), Times.Once); }
public SymbolValidationMessageHandlerLooseFacts() : base(MockBehavior.Loose) { SymbolPackage = new SymbolPackage() { Package = new Package() }; MessageData = new PackageValidationMessageData("packageId", "1.2.3", Guid.NewGuid(), ValidatingType.SymbolPackage); ValidationSet = new PackageValidationSet(); SymbolPackageValidatingEntity = new SymbolPackageValidatingEntity(SymbolPackage); CoreSymbolPackageServiceMock .Setup(ps => ps.FindPackageByIdAndVersionStrict(MessageData.PackageId, MessageData.PackageVersion)) .Returns(SymbolPackageValidatingEntity); ValidationSetProviderMock .Setup(vsp => vsp.TryGetOrCreateValidationSetAsync(MessageData, SymbolPackageValidatingEntity)) .ReturnsAsync(ValidationSet); }
public async Task <bool> HandleAsync(PackageValidationMessageData message) { if (message == null) { throw new ArgumentNullException(nameof(message)); } switch (message.Type) { case PackageValidationMessageType.CheckValidator: return(await CheckValidatorAsync(message.CheckValidator)); case PackageValidationMessageType.ProcessValidationSet: return(await ProcessValidationSetAsync(message.ProcessValidationSet, message.DeliveryCount)); default: throw new NotSupportedException($"The package validation message type '{message.Type}' is not supported."); } }
private PackageValidationSet InitializeValidationSet(PackageValidationMessageData message, IValidatingEntity <T> validatingEntity) { // If message would have the package Key the package will not need to be passed as an argument _logger.LogInformation("Initializing validation set {ValidationSetId} for package {PackageId} {PackageVersion} (package key {PackageKey})", message.ValidationTrackingId, message.PackageId, message.PackageVersion, validatingEntity.Key); var now = DateTime.UtcNow; var validationSet = new PackageValidationSet { Created = now, PackageId = message.PackageId, PackageNormalizedVersion = message.PackageNormalizedVersion, PackageKey = validatingEntity.Key, PackageValidations = new List <PackageValidation>(), Updated = now, ValidationTrackingId = message.ValidationTrackingId, ValidatingType = message.ValidatingType }; var validationsToStart = _validationConfiguration .Validations .Where(v => v.ShouldStart); foreach (var validation in validationsToStart) { var packageValidation = new PackageValidation { PackageValidationSet = validationSet, ValidationStatus = ValidationStatus.NotStarted, Type = validation.Name, ValidationStatusTimestamp = now, }; validationSet.PackageValidations.Add(packageValidation); } return(validationSet); }
public async Task DropsMessageIfPackageIsSoftDeleted() { var messageData = new PackageValidationMessageData("packageId", "1.2.3", Guid.NewGuid()); var validationConfiguration = new ValidationConfiguration(); var package = new Package { PackageStatusKey = PackageStatus.Deleted }; CorePackageServiceMock .Setup(ps => ps.FindPackageByIdAndVersionStrict(messageData.PackageId, messageData.PackageVersion)) .Returns(package); var handler = CreateHandler(); var result = await handler.HandleAsync(messageData); Assert.True(result); CorePackageServiceMock .Verify(ps => ps.FindPackageByIdAndVersionStrict(messageData.PackageId, messageData.PackageVersion), Times.Once); }
public async Task WaitsForValidationSetAvailabilityInValidationDBWithCheckValidator() { var messageData = PackageValidationMessageData.NewCheckValidator(Guid.NewGuid()); var validationConfiguration = new ValidationConfiguration(); ValidationSetProviderMock .Setup(ps => ps.TryGetParentValidationSetAsync(messageData.CheckValidator.ValidationId)) .ReturnsAsync((PackageValidationSet)null) .Verifiable(); var handler = CreateHandler(); var result = await handler.HandleAsync(messageData); ValidationSetProviderMock.Verify( ps => ps.TryGetParentValidationSetAsync(messageData.CheckValidator.ValidationId), Times.Once); Assert.False(result, "The handler should not have succeeded."); }
public void ProducesExpectedMessageForStartValidation() { // Arrange var input = PackageValidationMessageData.NewStartValidation( ValidationTrackingId, ContentType, ContentUrl, Properties); // Act var output = _target.SerializePackageValidationMessageData(input); // Assert Assert.Contains(SchemaVersionKey, output.Properties.Keys); Assert.Equal(SchemaVersion1, output.Properties[SchemaVersionKey]); Assert.Contains(SchemaName, output.Properties.Keys); Assert.Equal(StartValidationType, output.Properties[SchemaName]); var body = output.GetBody(); Assert.Equal(TestData.SerializedStartValidationData, body); }
public async Task WaitsForPackageAvailabilityInGalleryDBWithProcessValidationSet() { var messageData = PackageValidationMessageData.NewProcessValidationSet( "packageId", "1.2.3", Guid.NewGuid(), ValidatingType.SymbolPackage, entityKey: null); var validationConfiguration = new ValidationConfiguration(); CoreSymbolPackageServiceMock .Setup(ps => ps.FindPackageByIdAndVersionStrict(messageData.ProcessValidationSet.PackageId, messageData.ProcessValidationSet.PackageVersion)) .Returns <SymbolPackage>(null) .Verifiable(); var handler = CreateHandler(); await handler.HandleAsync(messageData); CoreSymbolPackageServiceMock.Verify( ps => ps.FindPackageByIdAndVersionStrict(messageData.ProcessValidationSet.PackageId, messageData.ProcessValidationSet.PackageVersion), Times.Once); }
public async Task DropsMessageAfterMissingPackageRetryCountIsReached() { var validationTrackingId = Guid.NewGuid(); var messageData = new PackageValidationMessageData("packageId", "1.2.3", validationTrackingId); CorePackageServiceMock .Setup(ps => ps.FindPackageByIdAndVersionStrict("packageId", "1.2.3")) .Returns <Package>(null) .Verifiable(); TelemetryServiceMock .Setup(t => t.TrackMissingPackageForValidationMessage("packageId", "1.2.3", validationTrackingId.ToString())) .Verifiable(); var handler = CreateHandler(); Assert.False(await handler.HandleAsync(OverrideDeliveryCount(messageData, deliveryCount: 1))); Assert.False(await handler.HandleAsync(OverrideDeliveryCount(messageData, deliveryCount: 2))); Assert.True(await handler.HandleAsync(OverrideDeliveryCount(messageData, deliveryCount: 3))); CorePackageServiceMock.Verify(ps => ps.FindPackageByIdAndVersionStrict("packageId", "1.2.3"), Times.Exactly(3)); TelemetryServiceMock.Verify(t => t.TrackMissingPackageForValidationMessage("packageId", "1.2.3", validationTrackingId.ToString()), Times.Once); }
public async Task RejectsNonSymbolPackageValidationSetWithCheckValidator() { var messageData = PackageValidationMessageData.NewCheckValidator(Guid.NewGuid()); var validationConfiguration = new ValidationConfiguration(); var validationSet = new PackageValidationSet { PackageKey = 42, ValidatingType = ValidatingType.Package }; ValidationSetProviderMock .Setup(ps => ps.TryGetParentValidationSetAsync(messageData.CheckValidator.ValidationId)) .ReturnsAsync(validationSet) .Verifiable(); var handler = CreateHandler(); var result = await handler.HandleAsync(messageData); ValidationSetProviderMock.Verify( ps => ps.TryGetParentValidationSetAsync(messageData.CheckValidator.ValidationId), Times.Once); Assert.False(result, "The handler should not have succeeded."); }
private async Task ScheduleCheckIfNotTimedOut( PackageValidationSet validationSet, IValidatingEntity <T> validatingEntity, bool scheduleNextCheck, bool tooLongNotificationAllowed) { var validationSetDuration = await UpdateValidationDurationAsync(validationSet, validatingEntity, tooLongNotificationAllowed); // Schedule another check if we haven't reached the validation set timeout yet. if (validationSetDuration <= _validationConfiguration.TimeoutValidationSetAfter) { if (scheduleNextCheck) { var messageData = PackageValidationMessageData.NewProcessValidationSet( validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, validationSet.ValidatingType, entityKey: validationSet.PackageKey); var postponeUntil = DateTimeOffset.UtcNow + _validationConfiguration.ValidationMessageRecheckPeriod; await _validationEnqueuer.SendMessageAsync(messageData, postponeUntil); } } else { _logger.LogWarning("Abandoning checking status of validation set {ValidationTrackingId} for " + "package {PackageId} {PackageVersion} because it took too long (Duration: {Duration}, CutOffDuration: {CutOffDuration})", validationSet.ValidationTrackingId, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSetDuration, _validationConfiguration.TimeoutValidationSetAfter); _telemetryService.TrackValidationSetTimeout(validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId); } }
public async Task GetOrCreateValidationSetAsyncDoesNotCreateDuplicateValidationSet() { Guid validationTrackingId = Guid.NewGuid(); var message = new PackageValidationMessageData(PackageValidationMessageData.PackageId, PackageValidationMessageData.PackageVersion, validationTrackingId); ValidationStorageMock .Setup(vs => vs.GetValidationSetAsync(validationTrackingId)) .ReturnsAsync((PackageValidationSet)null); ValidationStorageMock .Setup(vs => vs.OtherRecentValidationSetForPackageExists( PackageValidatingEntity, It.IsAny <TimeSpan>(), validationTrackingId)) .ReturnsAsync(true); var provider = CreateProvider(); var result = await provider.TryGetOrCreateValidationSetAsync(message, PackageValidatingEntity); Assert.Null(result); ValidationStorageMock .Verify( vs => vs.OtherRecentValidationSetForPackageExists( PackageValidatingEntity, It.IsAny <TimeSpan>(), validationTrackingId), Times.Once); ValidationStorageMock .Verify(vs => vs.CreateValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Never); PackageFileServiceMock.Verify( x => x.CopyPackageFileForValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Never); PackageFileServiceMock.Verify( x => x.CopyValidationPackageForValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Never); }
public async Task <PackageStatus> StartValidationAsync(Package package) { if (_appConfiguration.ReadOnlyMode) { throw new ReadOnlyModeException(Strings.CannotEnqueueDueToReadOnly); } var data = new PackageValidationMessageData( package.PackageRegistration.Id, package.Version, Guid.NewGuid()); var activityName = $"Enqueuing asynchronous package validation: " + $"{data.PackageId} {data.PackageVersion} ({data.ValidationTrackingId})"; using (_diagnosticsSource.Activity(activityName)) { await _enqueuer.StartValidationAsync(data); } // For now, don't require asynchronous validation before the package is available for consumption. // Related: https://github.com/NuGet/NuGetGallery/issues/4744 return(PackageStatus.Available); }
public async Task <PackageStatus> StartValidationAsync(TPackageEntity package) { var validatingType = ValidateAndGetType(package); var entityKey = package.Key == default(int) ? (int?)null : package.Key; var data = new PackageValidationMessageData( package.Id, package.Version, Guid.NewGuid(), validatingType, entityKey: entityKey); var activityName = $"Enqueuing asynchronous package validation: " + $"{data.PackageId} {data.PackageVersion} {data.ValidatingType} ({data.ValidationTrackingId})"; using (_diagnosticsSource.Activity(activityName)) { var postponeProcessingTill = DateTimeOffset.UtcNow + _appConfiguration.AsynchronousPackageValidationDelay; await _validationEnqueuer.StartValidationAsync(data, postponeProcessingTill); } return(TargetPackageStatus); }
public async Task ProcessValidationOutcomeAsync(PackageValidationSet validationSet, Package package) { var failedValidations = GetFailedValidations(validationSet); if (failedValidations.Any()) { _logger.LogWarning("Some validations failed for package {PackageId} {PackageVersion}, validation set {ValidationSetId}: {FailedValidations}", package.PackageRegistration.Id, package.NormalizedVersion, validationSet.ValidationTrackingId, failedValidations.Select(x => x.Type).ToList()); // The only way we can move to the failed validation state is if the package is currently in the // validating state. This has a beneficial side effect of only sending a failed validation email to the // customer when the package first moves to the failed validation state. If an admin comes along and // revalidates the package and the package fails validation again, we don't want another email going // out since that would be noisy for the customer. if (package.PackageStatusKey == PackageStatus.Validating) { await _packageStateProcessor.SetPackageStatusAsync(package, validationSet, PackageStatus.FailedValidation); var issuesExistAndAllPackageSigned = validationSet .PackageValidations .SelectMany(pv => pv.PackageValidationIssues) .Select(pvi => pvi.IssueCode == ValidationIssueCode.PackageIsSigned) .DefaultIfEmpty(false) .All(v => v); if (issuesExistAndAllPackageSigned) { _messageService.SendPackageSignedValidationFailedMessage(package); } else { _messageService.SendPackageValidationFailedMessage(package); } } else { // The case when validation fails while PackageStatus not validating is the case of // manual revalidation. In this case we don't want to take package down automatically // and let the person who requested revalidation to decide how to proceed. Ops will be // alerted by failed validation monitoring. _logger.LogInformation("Package {PackageId} {PackageVersion} was {PackageStatus} when validation set {ValidationSetId} failed. Will not mark it as failed.", package.PackageRegistration.Id, package.NormalizedVersion, package.PackageStatusKey, validationSet.ValidationTrackingId); } await CompleteValidationSetAsync(package, validationSet, isSuccess : false); } else if (AllValidationsSucceeded(validationSet)) { _logger.LogInformation("All validations are complete for the package {PackageId} {PackageVersion}, validation set {ValidationSetId}", package.PackageRegistration.Id, package.NormalizedVersion, validationSet.ValidationTrackingId); var fromStatus = package.PackageStatusKey; // Always set the package status to available so that processors can have a change to fix packages // that are already available. Processors should no-op when their work is already done, so the // modification of an already available package should be rare. The most common case for this is if // the processor has never been run on a package that was published before the processor was // implemented. In this case, the processor has to play catch-up. await _packageStateProcessor.SetPackageStatusAsync(package, validationSet, PackageStatus.Available); // Only send the email when first transitioning into the Available state. if (fromStatus != PackageStatus.Available) { _messageService.SendPackagePublishedMessage(package); } await CompleteValidationSetAsync(package, validationSet, isSuccess : true); } else { // There are no failed validations and some validations are still in progress. Update // the validation set's Updated field and send a notice if the validation set is taking // too long to complete. var previousUpdateTime = validationSet.Updated; await _validationStorageService.UpdateValidationSetAsync(validationSet); var validationSetDuration = validationSet.Updated - validationSet.Created; var previousDuration = previousUpdateTime - validationSet.Created; // Only send a "validating taking too long" notice once. This is ensured by verifying this is // the package's first validation set and that this is the first time the validation set duration // is greater than the configured threshold. Service Bus message duplication for a single validation // set will not cause multiple notices to be sent due to the row version on PackageValidationSet. if (validationSetDuration > _validationConfiguration.ValidationSetNotificationTimeout && previousDuration <= _validationConfiguration.ValidationSetNotificationTimeout && await _validationStorageService.GetValidationSetCountAsync(package.Key) == 1) { _logger.LogWarning("Sending message that validation set {ValidationTrackingId} for package {PackageId} {PackageVersion} is taking too long", validationSet.ValidationTrackingId, validationSet.PackageId, validationSet.PackageNormalizedVersion); _messageService.SendPackageValidationTakingTooLongMessage(package); _telemetryService.TrackSentValidationTakingTooLongMessage(package.PackageRegistration.Id, package.NormalizedVersion, validationSet.ValidationTrackingId); } // Track any validations that are past their expected thresholds. var timedOutValidations = GetIncompleteTimedOutValidations(validationSet); if (timedOutValidations.Any()) { foreach (var validation in timedOutValidations) { var duration = DateTime.UtcNow - validation.Started; _logger.LogWarning("Validation {Validation} for package {PackageId} {PackageVersion} is past its expected duration after {Duration}", validation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, duration); _telemetryService.TrackValidatorTimeout(validation.Type); } } // Schedule another check if we haven't reached the validation set timeout yet. if (validationSetDuration <= _validationConfiguration.TimeoutValidationSetAfter) { var messageData = new PackageValidationMessageData(package.PackageRegistration.Id, package.Version, validationSet.ValidationTrackingId); var postponeUntil = DateTimeOffset.UtcNow + _validationConfiguration.ValidationMessageRecheckPeriod; await _validationEnqueuer.StartValidationAsync(messageData, postponeUntil); } else { _telemetryService.TrackValidationSetTimeout(package.PackageRegistration.Id, package.NormalizedVersion, validationSet.ValidationTrackingId); } } }
public async Task CopiesToValidationSetContainerBeforeAddingDbRecord() { const string validation1 = "validation1"; Configuration.Validations = new List <ValidationConfigurationItem> { new ValidationConfigurationItem { Name = validation1, TrackAfter = TimeSpan.FromDays(1), RequiredValidations = new List <string>(), ShouldStart = true, } }; Package.PackageStatusKey = PackageStatus.Available; var operations = new List <string>(); Guid validationTrackingId = Guid.NewGuid(); ValidationStorageMock .Setup(vs => vs.GetValidationSetAsync(validationTrackingId)) .ReturnsAsync((PackageValidationSet)null) .Verifiable(); ValidationStorageMock .Setup(vs => vs.OtherRecentValidationSetForPackageExists(It.IsAny <IValidatingEntity <Package> >(), It.IsAny <TimeSpan>(), validationTrackingId)) .ReturnsAsync(false); PackageFileServiceMock .Setup(x => x.CopyPackageFileForValidationSetAsync(It.IsAny <PackageValidationSet>())) .ReturnsAsync(ETag) .Callback <PackageValidationSet>(_ => operations.Add(nameof(IValidationFileService.CopyPackageFileForValidationSetAsync))); PackageFileServiceMock .Setup(x => x.BackupPackageFileFromValidationSetPackageAsync(It.IsAny <PackageValidationSet>())) .Returns(Task.CompletedTask) .Callback(() => operations.Add(nameof(IValidationFileService.BackupPackageFileFromValidationSetPackageAsync))); ValidationStorageMock .Setup(vs => vs.CreateValidationSetAsync(It.IsAny <PackageValidationSet>())) .Returns <PackageValidationSet>(pvs => Task.FromResult(pvs)) .Callback <PackageValidationSet>(_ => operations.Add(nameof(IValidationStorageService.CreateValidationSetAsync))); ValidationStorageMock .Setup(vs => vs.GetValidationSetCountAsync(It.IsAny <IValidatingEntity <Package> >())) .ReturnsAsync(1); var provider = CreateProvider(); var packageValidationMessageData = new PackageValidationMessageData( Package.PackageRegistration.Id, Package.NormalizedVersion, validationTrackingId); await provider.TryGetOrCreateValidationSetAsync(packageValidationMessageData, new PackageValidatingEntity(Package)); Assert.Equal(new[] { nameof(IValidationFileService.CopyPackageFileForValidationSetAsync), nameof(IValidationFileService.BackupPackageFileFromValidationSetPackageAsync), nameof(IValidationStorageService.CreateValidationSetAsync), }, operations); }
public ValidationSetProviderFacts() { ValidationStorageMock = new Mock <IValidationStorageService>(MockBehavior.Strict); PackageFileServiceMock = new Mock <IValidationFileService>(MockBehavior.Strict); ValidatorProvider = new Mock <IValidatorProvider>(MockBehavior.Strict); ConfigurationAccessorMock = new Mock <IOptionsSnapshot <ValidationConfiguration> >(); TelemetryServiceMock = new Mock <ITelemetryService>(); LoggerMock = new Mock <ILogger <ValidationSetProvider <Package> > >(); PackageFileServiceMock .Setup(x => x.CopyPackageFileForValidationSetAsync(It.IsAny <PackageValidationSet>())) .ReturnsAsync(() => ETag); PackageFileServiceMock .Setup(x => x.CopyValidationPackageForValidationSetAsync(It.IsAny <PackageValidationSet>())) .Returns(Task.CompletedTask); PackageFileServiceMock .Setup(x => x.BackupPackageFileFromValidationSetPackageAsync(It.IsAny <PackageValidationSet>())) .Returns(Task.CompletedTask); ValidatorProvider .Setup(x => x.IsProcessor(It.IsAny <string>())) .Returns(true); Configuration = new ValidationConfiguration(); ConfigurationAccessorMock .SetupGet(ca => ca.Value) .Returns(() => Configuration); ETag = "\"some-etag\""; Package = new Package { PackageRegistration = new PackageRegistration { Id = "package1" }, Version = "1.2.3.456", NormalizedVersion = "1.2.3", Key = 42, Created = new DateTime(2010, 1, 2, 8, 30, 0, DateTimeKind.Utc), PackageStatusKey = PackageStatus.Validating, }; Package.PackageRegistration.Packages = new List <Package> { Package }; ValidationSet = new PackageValidationSet { PackageId = Package.PackageRegistration.Id, PackageNormalizedVersion = Package.NormalizedVersion, PackageKey = Package.Key, ValidationTrackingId = Guid.NewGuid(), }; PackageValidationMessageData = new PackageValidationMessageData( Package.PackageRegistration.Id, Package.NormalizedVersion, ValidationSet.ValidationTrackingId); PackageValidatingEntity = new PackageValidatingEntity(Package); }
public async Task DoesNotCreateValidationsWhenShouldStartFalse() { const string validation1 = "validation1"; const string validation2 = "validation2"; Configuration.Validations = new List <ValidationConfigurationItem> { new ValidationConfigurationItem() { Name = validation1, TrackAfter = TimeSpan.FromDays(1), RequiredValidations = new List <string>(), ShouldStart = true, }, new ValidationConfigurationItem() { Name = validation2, TrackAfter = TimeSpan.FromDays(1), RequiredValidations = new List <string>(), ShouldStart = false, } }; Guid validationTrackingId = Guid.NewGuid(); ValidationStorageMock .Setup(vs => vs.GetValidationSetAsync(validationTrackingId)) .ReturnsAsync((PackageValidationSet)null) .Verifiable(); ValidationStorageMock .Setup(vs => vs.OtherRecentValidationSetForPackageExists(It.IsAny <IValidatingEntity <Package> >(), It.IsAny <TimeSpan>(), validationTrackingId)) .ReturnsAsync(false); PackageValidationSet createdSet = null; ValidationStorageMock .Setup(vs => vs.CreateValidationSetAsync(It.IsAny <PackageValidationSet>())) .Returns <PackageValidationSet>(pvs => Task.FromResult(pvs)) .Callback <PackageValidationSet>(pvs => createdSet = pvs) .Verifiable(); ValidationStorageMock .Setup(vs => vs.GetValidationSetCountAsync(It.IsAny <IValidatingEntity <Package> >())) .ReturnsAsync(1); var provider = new ValidationSetProvider <Package>( ValidationStorageMock.Object, PackageFileServiceMock.Object, ValidatorProvider.Object, ConfigurationAccessorMock.Object, TelemetryServiceMock.Object, LoggerMock.Object); var packageValidationMessageData = new PackageValidationMessageData( Package.PackageRegistration.Id, Package.NormalizedVersion, validationTrackingId); var returnedSet = await provider.TryGetOrCreateValidationSetAsync(packageValidationMessageData, PackageValidatingEntity); var endOfCallTimestamp = DateTime.UtcNow; ValidationStorageMock .Verify(vs => vs.CreateValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Once); Assert.NotNull(returnedSet); Assert.NotNull(createdSet); Assert.Same(createdSet, returnedSet); Assert.Equal(Package.PackageRegistration.Id, createdSet.PackageId); Assert.Equal(Package.NormalizedVersion, createdSet.PackageNormalizedVersion); Assert.Equal(Package.Key, createdSet.PackageKey); Assert.Equal(validationTrackingId, createdSet.ValidationTrackingId); Assert.True(createdSet.Created.Kind == DateTimeKind.Utc); Assert.True(createdSet.Updated.Kind == DateTimeKind.Utc); var allowedTimeDifference = TimeSpan.FromSeconds(5); Assert.True(endOfCallTimestamp - createdSet.Created < allowedTimeDifference); Assert.True(endOfCallTimestamp - createdSet.Updated < allowedTimeDifference); Assert.All(createdSet.PackageValidations, v => Assert.Same(createdSet, v.PackageValidationSet)); Assert.All(createdSet.PackageValidations, v => Assert.Equal(ValidationStatus.NotStarted, v.ValidationStatus)); Assert.All(createdSet.PackageValidations, v => Assert.True(endOfCallTimestamp - v.ValidationStatusTimestamp < allowedTimeDifference)); Assert.Contains(createdSet.PackageValidations, v => v.Type == validation1); Assert.DoesNotContain(createdSet.PackageValidations, v => v.Type == validation2); PackageFileServiceMock.Verify( x => x.CopyValidationPackageForValidationSetAsync(returnedSet), Times.Once); TelemetryServiceMock.Verify( x => x.TrackDurationToValidationSetCreation(createdSet.Created - Package.Created), Times.Once); }
public async Task <PackageValidationSet> TryGetOrCreateValidationSetAsync(PackageValidationMessageData message, IValidatingEntity <T> validatingEntity) { var validationSet = await _validationStorageService.GetValidationSetAsync(message.ValidationTrackingId); if (validationSet == null) { var shouldSkip = await _validationStorageService.OtherRecentValidationSetForPackageExists( validatingEntity, _validationConfiguration.NewValidationRequestDeduplicationWindow, message.ValidationTrackingId); if (shouldSkip) { return(null); } validationSet = InitializeValidationSet(message, validatingEntity); if (validatingEntity.Status == PackageStatus.Available) { var packageETag = await _packageFileService.CopyPackageFileForValidationSetAsync(validationSet); // This indicates that the package in the package container is expected to not change. validationSet.PackageETag = packageETag; } else { await _packageFileService.CopyValidationPackageForValidationSetAsync(validationSet); // A symbols package for the same id and version can be re-submitted. // When this happens a new validation is submitted. After validation the new symbols package will overwrite the old symbols package. // Because of this when a new validation for a symbols package is received it can already exist a symbols package in the public symbols container. if (validatingEntity.ValidatingType == ValidatingType.SymbolPackage) { validationSet.PackageETag = await _packageFileService.GetPublicPackageBlobETagOrNullAsync(validationSet); } else { // This indicates that the package in the packages container is expected to not exist (i.e. it has // has no etag at all). validationSet.PackageETag = null; } } // If there are any processors in the validation set, back up the original. We back up from the // validation set copy to avoid concurrency issues. if (validationSet.PackageValidations.Any(x => _validatorProvider.IsProcessor(x.Type))) { await _packageFileService.BackupPackageFileFromValidationSetPackageAsync(validationSet); } validationSet = await PersistValidationSetAsync(validationSet, validatingEntity); } else { var sameKey = validatingEntity.Key == validationSet.PackageKey; if (!sameKey) { throw new InvalidOperationException($"Validation set key ({validationSet.PackageKey}) " + $"does not match expected {validatingEntity.EntityRecord.GetType().Name} key ({validatingEntity.Key})."); } } return(validationSet); }
public async Task <bool> HandleAsync(SymbolsValidatorMessage message) { using (_logger.BeginScope("{ValidatorName}: Handling message for {PackageId} {PackageVersion} validation set {ValidationId}", ValidatorName.SymbolsValidator, message.PackageId, message.PackageNormalizedVersion, message.ValidationId)) { if (message == null) { throw new ArgumentNullException(nameof(message)); } var validation = await _validatorStateService.GetStatusAsync(message.ValidationId); // A validation should be queued with ValidatorState == Incomplete. if (validation == null) { _logger.LogInformation( "{ValidatorName} : Could not find validation entity, requeueing (package: {PackageId} {PackageVersion}, validationId: {ValidationId})", ValidatorName.SymbolsValidator, message.PackageId, message.PackageNormalizedVersion, message.ValidationId); // Message may be retried. return(false); } else if (validation.State == ValidationStatus.NotStarted) { _logger.LogWarning( "{ValidatorName}:Unexpected status '{ValidatorState}' when 'Incomplete' was expected, requeueing package id: {PackageId} package version: {PackageVersion} validation id: {ValidationId})", ValidatorName.SymbolsValidator, validation.State, message.PackageId, message.PackageNormalizedVersion, message.ValidationId); // Message may be retried. return(false); } // Final states else if (validation.State == ValidationStatus.Failed || validation.State == ValidationStatus.Succeeded) { _logger.LogWarning( "{ValidatorName}:Terminal symbol verification status '{ValidatorState}' when 'Incomplete' was expected, dropping message (package id: {PackageId} package version: {PackageVersion} validation id: {ValidationId})", ValidatorName.SymbolsValidator, validation.State, message.PackageId, message.PackageNormalizedVersion, message.ValidationId); // Consume the message. return(true); } var validationResult = await _symbolValidatorService.ValidateSymbolsAsync(message, CancellationToken.None); if (validationResult.Status == ValidationStatus.Failed || validationResult.Status == ValidationStatus.Succeeded) { validation.State = validationResult.Status; validation.ValidatorIssues = validation.ValidatorIssues ?? new List <ValidatorIssue>(); foreach (var issue in validationResult.Issues) { validation.ValidatorIssues.Add(new ValidatorIssue { IssueCode = issue.IssueCode, Data = issue.Serialize(), }); } if (validationResult.NupkgUrl != null) { validation.NupkgUrl = validationResult.NupkgUrl; } var completed = await SaveStatusAsync(validation, message, MaxDBSaveRetry); if (completed && _featureFlagService.IsQueueBackEnabled()) { _logger.LogInformation("Sending queue-back message for validation {ValidationId}.", message.ValidationId); var messageData = PackageValidationMessageData.NewCheckValidator(message.ValidationId); await _validationEnqueuer.SendMessageAsync(messageData); } return(completed); } _logger.LogWarning( "{ValidatorName}:The validation did not return a complete status for package {PackageId} {PackageVersion} for validation id: {ValidationId} .", ValidatorName.SymbolsValidator, message.PackageId, message.PackageNormalizedVersion, message.ValidationId); return(false); } }
private async Task <bool> ProcessValidationSetAsync(ProcessValidationSetData message, int deliveryCount) { using (_logger.BeginScope( "Handling process validation set message for {ValidatingType} {PackageId} {PackageVersion} {Key} " + "{ValidationSetId}", ValidatingType, message.PackageId, message.PackageNormalizedVersion, message.EntityKey, message.ValidationTrackingId)) { // When a message is sent from the Gallery with validation of a new entity, the EntityKey will be null // because the message is sent to the service bus before the entity is persisted in the DB. However // when a revalidation happens or when the message is re-sent by the orchestrator the message will // contain the key. In this case the key is used to find the entity to validate. var entity = message.EntityKey.HasValue ? _entityService.FindPackageByKey(message.EntityKey.Value) : _entityService.FindPackageByIdAndVersionStrict(message.PackageId, message.PackageNormalizedVersion); if (entity == null) { // no package in DB yet. Might have received message a bit early, need to retry later if (deliveryCount - 1 >= _configs.MissingPackageRetryCount) { _logger.LogWarning( "Could not find {ValidatingType} {PackageId} {PackageVersion} {Key} in DB after " + "{DeliveryCount} tries. Discarding the message.", ValidatingType, message.PackageId, message.PackageNormalizedVersion, message.EntityKey, deliveryCount); _telemetryService.TrackMissingPackageForValidationMessage( message.PackageId, message.PackageNormalizedVersion, message.ValidationTrackingId.ToString()); return(true); } else { _logger.LogInformation( "Could not find {ValidatingType} {PackageId} {PackageVersion} {Key} in DB. Retrying.", ValidatingType, message.PackageId, message.PackageNormalizedVersion, message.EntityKey); return(false); } } // Immediately halt validation of a soft deleted package. if (ShouldNoOpDueToDeletion && entity.Status == PackageStatus.Deleted) { _logger.LogWarning( "{ValidatingType} {PackageId} {PackageNormalizedVersion} {Key} is soft deleted. Dropping " + "message for validation set {ValidationSetId}.", ValidatingType, message.PackageId, message.PackageNormalizedVersion, entity.Key, message.ValidationTrackingId); return(true); } var lease = await TryAcquireLeaseAsync(message.PackageId, message.PackageNormalizedVersion); if (lease.State == LeaseContextState.Unavailable) { _logger.LogInformation( "The lease {ResourceName} is unavailable. Retrying this message in {LeaseTime}.", lease.ResourceName, LeaseTime); var messageData = PackageValidationMessageData.NewProcessValidationSet( message.PackageId, message.PackageNormalizedVersion, message.ValidationTrackingId, message.ValidatingType, message.EntityKey); var postponeUntil = DateTimeOffset.UtcNow + LeaseTime; await _validationEnqueuer.SendMessageAsync(messageData, postponeUntil); return(true); } try { var validationSet = await _validationSetProvider.TryGetOrCreateValidationSetAsync(message, entity); if (validationSet == null) { _logger.LogInformation( "The validation request for {ValidatingType} {PackageId} {PackageVersion} {Key} " + "{ValidationSetId} is a duplicate. Discarding the message.", ValidatingType, message.PackageId, message.PackageNormalizedVersion, entity.Key, message.ValidationTrackingId); return(true); } await ProcessValidationSetAsync(entity, validationSet, scheduleNextCheck : true); } finally { await TryReleaseLeaseAsync(lease); } } return(true); }
private async Task <bool> HandleAsync(SignatureValidationMessage message, CancellationToken cancellationToken) { using (_logger.BeginScope("Handling signature validation message for package {PackageId} {PackageVersion}, validation {ValidationId}", message.PackageId, message.PackageVersion, message.ValidationId)) { // Find the signature validation entity that matches this message. var validation = await _validatorStateService.GetStatusAsync(message.ValidationId); // A signature validation should be queued with ValidatorState == Incomplete. if (validation == null) { _logger.LogInformation( "Could not find validation entity, requeueing (package: {PackageId} {PackageVersion}, validationId: {ValidationId})", message.PackageId, message.PackageVersion, message.ValidationId); // Message may be retried. return(false); } else if (validation.State == ValidationStatus.NotStarted) { _logger.LogWarning( "Unexpected signature verification status '{ValidatorState}' when 'Incomplete' was expected, requeueing (package id: {PackageId} package version: {PackageVersion} validation id: {ValidationId})", validation.State, message.PackageId, message.PackageVersion, message.ValidationId); // Message may be retried. return(false); } else if (validation.State != ValidationStatus.Incomplete) { _logger.LogWarning( "Terminal signature verification status '{ValidatorState}' when 'Incomplete' was expected, dropping message (package id: {PackageId} package version: {PackageVersion} validation id: {ValidationId})", validation.State, message.PackageId, message.PackageVersion, message.ValidationId); // Consume the message. return(true); } // Validate package using (var packageStream = await _packageDownloader.DownloadAsync(message.NupkgUri, cancellationToken)) { var result = await _signatureValidator.ValidateAsync( validation.PackageKey, packageStream, message, cancellationToken); validation.State = result.State; // Save any issues if the resulting state is terminal. if (validation.State == ValidationStatus.Failed || validation.State == ValidationStatus.Succeeded) { validation.ValidatorIssues = validation.ValidatorIssues ?? new List <ValidatorIssue>(); foreach (var issue in result.Issues) { validation.ValidatorIssues.Add(new ValidatorIssue { IssueCode = issue.IssueCode, Data = issue.Serialize(), }); } } // Save the .nupkg URL if the resulting state is successful. if (validation.State == ValidationStatus.Succeeded && result.NupkgUri != null) { validation.NupkgUrl = result.NupkgUri.AbsoluteUri; } } // The signature validator should do all of the work to bring this validation to its completion. if (validation.State != ValidationStatus.Succeeded && validation.State != ValidationStatus.Failed) { _logger.LogError("The signature validator should have set the status 'Succeeded' or 'Failed', not " + "'{ValidatorState}' (package id: {PackageId} package version: {PackageVersion} validation id: {ValidationId})", validation.State, message.PackageId, message.PackageVersion, message.ValidationId); return(false); } // Save the resulting validation status. var completed = await SaveStatusAsync(validation, message); if (completed && _featureFlagService.IsQueueBackEnabled()) { _logger.LogInformation("Sending queue-back message for validation {ValidationId}.", message.ValidationId); var messageData = PackageValidationMessageData.NewCheckValidator(message.ValidationId); await _validationEnqueuer.SendMessageAsync(messageData); } return(completed); } }
/// <summary> /// Perform the certificate validation request, including online revocation checks. /// </summary> /// <param name="message">The message requesting the certificate validation.</param> /// <returns>Whether the validation completed. If false, the validation should be retried later.</returns> public async Task <bool> HandleAsync(CertificateValidationMessage message) { using (_logger.BeginScope("Handling validate certificate message {CertificateKey} {ValidationId}", message.CertificateKey, message.ValidationId)) { var validation = await _certificateValidationService.FindCertificateValidationAsync(message); if (validation == null) { _logger.LogInformation( "Could not find a certificate validation entity, failing (certificate: {CertificateKey} validation: {ValidationId})", message.CertificateKey, message.ValidationId); return(false); } if (validation.Status != null) { // A certificate validation should be queued with a Status of null, and once the certificate validation // completes, the Status should be updated to a non-null value. Hence, the Status here SHOULD be null. // A non-null Status may indicate message duplication. _logger.LogWarning( "Invalid certificate validation entity's status, dropping message (certificate: {CertificateThumbprint} validation: {ValidationId})", validation.EndCertificate.Thumbprint, validation.ValidationId); return(true); } if (validation.EndCertificate.Status == EndCertificateStatus.Revoked) { if (message.RevalidateRevokedCertificate) { _logger.LogWarning( "Revalidating certificate that is known to be revoked " + "(certificate: {CertificateThumbprint} validation: {ValidationId})", validation.EndCertificate.Thumbprint, validation.ValidationId); } else { // Do NOT revalidate a certificate that is known to be revoked unless explicitly told to! // Certificate Authorities are not required to keep a certificate's revocation information // forever, therefore, revoked certificates should only be revalidated in special cases. _logger.LogError( "Certificate known to be revoked MUST be validated with the " + $"{nameof(CertificateValidationMessage.RevalidateRevokedCertificate)} flag enabled " + "(certificate: {CertificateThumbprint} validation: {ValidationId})", validation.EndCertificate.Thumbprint, validation.ValidationId); return(true); } } CertificateVerificationResult result; using (var certificates = await LoadCertificatesAsync(validation)) { switch (validation.EndCertificate.Use) { case EndCertificateUse.CodeSigning: result = _certificateVerifier.VerifyCodeSigningCertificate( certificates.EndCertificate, certificates.AncestorCertificates); break; case EndCertificateUse.Timestamping: result = _certificateVerifier.VerifyTimestampingCertificate( certificates.EndCertificate, certificates.AncestorCertificates); break; default: throw new InvalidOperationException($"Unknown {nameof(EndCertificateUse)}: {validation.EndCertificate.Use}"); } } // Save the result. This may alert if packages are invalidated. if (!await _certificateValidationService.TrySaveResultAsync(validation, result)) { _logger.LogWarning( "Failed to save certificate validation result " + "(certificate: {CertificateThumbprint} validation: {ValidationId}), " + "retrying validation...", validation.EndCertificate.Thumbprint, validation.ValidationId); return(false); } var completed = HasValidationCompleted(validation, result); if (completed && message.SendCheckValidator && _featureFlagService.IsQueueBackEnabled()) { _logger.LogInformation("Sending queue-back message for validation {ValidationId}.", message.ValidationId); var messageData = PackageValidationMessageData.NewCheckValidator(message.ValidationId); await _validationEnqueuer.SendMessageAsync(messageData); } return(completed); } }