public async Task DoesNotEmitTelemetryIfMultipleValidationSetsExist() { const string validation1 = "validation1"; Configuration.Validations = new List <ValidationConfigurationItem> { new ValidationConfigurationItem() { Name = validation1, TrackAfter = TimeSpan.FromDays(1), RequiredValidations = new List <string> { } } }; Guid validationTrackingId = Guid.NewGuid(); ValidationStorageMock .Setup(vs => vs.GetValidationSetAsync(validationTrackingId)) .ReturnsAsync((PackageValidationSet)null) .Verifiable(); 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(2); ValidationStorageMock .Setup(vs => vs.OtherRecentValidationSetForPackageExists(It.IsAny <IValidatingEntity <Package> >(), It.IsAny <TimeSpan>(), It.IsAny <Guid>())) .ReturnsAsync(false); var provider = new ValidationSetProvider <Package>( ValidationStorageMock.Object, PackageFileServiceMock.Object, ValidatorProvider.Object, ConfigurationAccessorMock.Object, TelemetryServiceMock.Object, LoggerMock.Object); var packageValidationMessageData = new ProcessValidationSetData( Package.PackageRegistration.Id, Package.NormalizedVersion, validationTrackingId, ValidatingType.Package, Package.Key); var returnedSet = await provider.TryGetOrCreateValidationSetAsync(packageValidationMessageData, PackageValidatingEntity); ValidationStorageMock .Verify(vs => vs.CreateValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Once); TelemetryServiceMock.Verify( x => x.TrackDurationToValidationSetCreation(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <Guid>(), It.IsAny <TimeSpan>()), Times.Never); }
public async Task CopiesPackageFromValidationContainerWhenNotAvailable(PackageStatus packageStatus) { 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; 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 = CreateProvider(); var packageValidationMessageData = new ProcessValidationSetData( Package.PackageRegistration.Id, Package.NormalizedVersion, validationTrackingId, ValidatingType.Package, Package.Key); var actual = await provider.TryGetOrCreateValidationSetAsync(packageValidationMessageData, PackageValidatingEntity); PackageFileServiceMock.Verify(x => x.CopyPackageFileForValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Never); PackageFileServiceMock.Verify(x => x.CopyValidationPackageForValidationSetAsync(createdSet), Times.Once); PackageFileServiceMock.Verify(x => x.CopyValidationPackageForValidationSetAsync(It.IsAny <PackageValidationSet>()), Times.Once); PackageFileServiceMock.Verify(x => x.BackupPackageFileFromValidationSetPackageAsync(createdSet), Times.Once); Assert.Null(actual.PackageETag); }
public async Task DoesNotBackUpThePackageWhenThereAreNoValidators() { const string validation1 = "validation1"; Configuration.Validations = new List <ValidationConfigurationItem> { new ValidationConfigurationItem() { Name = validation1, TrackAfter = TimeSpan.FromDays(1), RequiredValidations = new List <string> { } } }; ValidatorProvider .Setup(x => x.IsNuGetProcessor(validation1)) .Returns(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 = CreateProvider(); var packageValidationMessageData = new ProcessValidationSetData( Package.PackageRegistration.Id, Package.NormalizedVersion, validationTrackingId, ValidatingType.Package, Package.Key); var actual = await provider.TryGetOrCreateValidationSetAsync(packageValidationMessageData, PackageValidatingEntity); PackageFileServiceMock.Verify( x => x.BackupPackageFileFromValidationSetPackageAsync(It.IsAny <PackageValidationSet>()), Times.Never); }
private PackageValidationSet InitializeValidationSet(ProcessValidationSetData 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, ValidationSetStatus = ValidationSetStatus.InProgress, }; 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 GetOrCreateValidationSetAsyncDoesNotCreateDuplicateValidationSet() { Guid validationTrackingId = Guid.NewGuid(); var message = new ProcessValidationSetData( PackageValidationMessageData.PackageId, PackageValidationMessageData.PackageVersion, validationTrackingId, ValidatingType.Package, PackageValidationMessageData.EntityKey); 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 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 ProcessValidationSetData( Package.PackageRegistration.Id, Package.NormalizedVersion, validationTrackingId, ValidatingType.Package, Package.Key); 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.IsNuGetProcessor(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 ProcessValidationSetData( Package.PackageRegistration.Id, Package.NormalizedVersion, ValidationSet.ValidationTrackingId, ValidatingType.Package, Package.Key); 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 ProcessValidationSetData( Package.PackageRegistration.Id, Package.NormalizedVersion, validationTrackingId, ValidatingType.Package, Package.Key); 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.PackageId, createdSet.PackageNormalizedVersion, createdSet.ValidationTrackingId, createdSet.Created - Package.Created), Times.Once); }
public async Task <PackageValidationSet> TryGetOrCreateValidationSetAsync(ProcessValidationSetData 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.IsNuGetProcessor(x.Type))) { await _packageFileService.BackupPackageFileFromValidationSetPackageAsync(validationSet, _sasDefinitionConfiguration.ValidationSetProviderSasDefinition); } 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); }
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); }