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);
        }
Пример #2
0
        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);
        }
Пример #5
0
        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);
            }
Пример #7
0
            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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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.");
            }
        }
Пример #11
0
        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);
            }
        }
Пример #19
0
        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);
        }
Пример #21
0
        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);
        }
Пример #22
0
        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);
                }
            }
        }
Пример #23
0
        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);
        }
Пример #24
0
        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);
        }
Пример #25
0
        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);
        }
Пример #26
0
        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);
        }
Пример #27
0
        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);
            }
        }
Пример #28
0
        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);
        }
Пример #29
0
        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);
            }
        }