public async Task <ValidationSetProcessorResult> ProcessValidationsAsync(PackageValidationSet validationSet) { _logger.LogInformation("Starting processing validation request for {PackageId} {PackageVersion}, validation set {ValidationSetId}", validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId); var processorStats = new ValidationSetProcessorResult(); int loopLimit = MaxProcessAttempts; await ProcessIncompleteValidations(validationSet, processorStats); bool hadSucceededValidations = false; do { // we will try to start more validations in case previous validation start attempts // result in Succeeded validation immediately (i.e. the validation was synchronous). // If no validation start attempts resulted in succeeded validation (ProcessNotStartedValidations // returns false) we move on and will check on progress later. // loopLimit is there to prevent looping here infinitely if there are any bugs that // cause ProcessNotStartedValidations to always return true. hadSucceededValidations = await ProcessNotStartedValidations(validationSet, processorStats); } while (hadSucceededValidations && loopLimit-- > 0); if (loopLimit <= 0) { _logger.LogWarning("Too many processing attempts ({NumAttempts}) for {PackageId} {PackageVersion}, validation set {ValidationSetId}", MaxProcessAttempts, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId); } return(processorStats); }
private void UpdateStatsForValidationSuccess(ValidationSetProcessorResult processorStats, ValidationConfigurationItem validationConfiguration) { processorStats.AnyValidationSucceeded = true; if (validationConfiguration.FailureBehavior == ValidationFailureBehavior.MustSucceed) { processorStats.AnyRequiredValidationSucceeded = true; } }
public async Task ProcessValidationOutcomeAsync(PackageValidationSet validationSet, IValidatingEntity <T> validatingEntity, ValidationSetProcessorResult currentCallStats) { var failedValidations = GetFailedValidations(validationSet); if (failedValidations.Any()) { _logger.LogWarning("Some validations failed for package {PackageId} {PackageVersion}, validation set {ValidationSetId}: {FailedValidations}", validationSet.PackageId, validationSet.PackageNormalizedVersion, 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 (validatingEntity.Status == PackageStatus.Validating) { await _packageStateProcessor.SetStatusAsync(validatingEntity, validationSet, PackageStatus.FailedValidation); await MarkValidationSetAsCompletedAsync(validationSet); await _messageService.SendValidationFailedMessageAsync(validatingEntity.EntityRecord, validationSet); } 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.", validationSet.PackageId, validationSet.PackageNormalizedVersion, validatingEntity.Status, validationSet.ValidationTrackingId); await MarkValidationSetAsCompletedAsync(validationSet); } TrackValidationSetCompletion(validationSet, isSuccess: false); await _packageFileService.DeletePackageForValidationSetAsync(validationSet); } else if (AllRequiredValidationsSucceeded(validationSet)) { _logger.LogInformation("All validations are complete for the package {PackageId} {PackageVersion}, validation set {ValidationSetId}", validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId); var fromStatus = validatingEntity.Status; // Always set the package status to available so that processors can have a chance 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.SetStatusAsync(validatingEntity, validationSet, PackageStatus.Available); var areOptionalValidationsRunning = AreOptionalValidationsRunning(validationSet); if (!areOptionalValidationsRunning) { await MarkValidationSetAsCompletedAsync(validationSet); } // Only send the email when first transitioning into the Available state. if (fromStatus != PackageStatus.Available) { await _messageService.SendPublishedMessageAsync(validatingEntity.EntityRecord); } if (currentCallStats.AnyRequiredValidationSucceeded) { TrackValidationSetCompletion(validationSet, isSuccess: true); } if (areOptionalValidationsRunning) { await ScheduleCheckIfNotTimedOut(validationSet, validatingEntity, tooLongNotificationAllowed : false); } // TODO: implement delayed cleanup that would allow internal services // to access original packages for some time after package become available: // https://github.com/NuGet/Engineering/issues/2506 } else { await ScheduleCheckIfNotTimedOut(validationSet, validatingEntity, tooLongNotificationAllowed : true); } }
private async Task ProcessIncompleteValidations(PackageValidationSet validationSet, ValidationSetProcessorResult processorStats) { foreach (var packageValidation in validationSet.PackageValidations.Where(v => v.ValidationStatus == ValidationStatus.Incomplete)) { using (_logger.BeginScope("Incomplete {ValidationType} Key {ValidationId}", packageValidation.Type, packageValidation.Key)) { _logger.LogInformation("Processing incomplete validation {ValidationType} for {PackageId} {PackageVersion}, validation set {ValidationSetId}, {ValidationId}", packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, packageValidation.Key); var validationConfiguration = GetValidationConfiguration(packageValidation.Type); if (validationConfiguration == null) { await OnUnknownValidation(packageValidation); continue; } var validator = _validatorProvider.GetValidator(packageValidation.Type); var validationRequest = await CreateValidationRequest(packageValidation.PackageValidationSet, packageValidation); var validationResult = await validator.GetResultAsync(validationRequest); if (validationResult.Status != ValidationStatus.Incomplete) { _logger.LogInformation( "New status for validation {ValidationType} for {PackageId} {PackageVersion} is " + "{ValidationStatus}, validation set {ValidationSetId}, {ValidationId}", packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationResult.Status, validationSet.ValidationTrackingId, packageValidation.Key); } else { _logger.LogDebug( "Validation {ValidationType} for {PackageId} {PackageVersion} is already " + "{ValidationStatus}, validation set {ValidationSetId}, {ValidationId}", packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationResult.Status, validationSet.ValidationTrackingId, packageValidation.Key); } switch (validationResult.Status) { case ValidationStatus.Incomplete: break; case ValidationStatus.Failed: await _validationStorageService.UpdateValidationStatusAsync(packageValidation, validationResult); await validator.CleanUpAsync(validationRequest); break; case ValidationStatus.Succeeded: await _validationStorageService.UpdateValidationStatusAsync(packageValidation, validationResult); await validator.CleanUpAsync(validationRequest); UpdateStatsForValidationSuccess(processorStats, validationConfiguration); break; default: throw new InvalidOperationException($"Unexpected validation state: " + $"DB: {ValidationStatus.Incomplete} ({(int)ValidationStatus.Incomplete}), " + $"Actual: {validationResult.Status} {(int)validationResult.Status}"); } } } }
private async Task <bool> ProcessNotStartedValidations(PackageValidationSet validationSet, ValidationSetProcessorResult processorStats) { bool tryMoreValidations = false; foreach (var packageValidation in validationSet.PackageValidations.Where(v => v.ValidationStatus == ValidationStatus.NotStarted)) { using (_logger.BeginScope("Not started {ValidationType} Key {ValidationId}", packageValidation.Type, packageValidation.Key)) { _logger.LogInformation("Processing not started validation {ValidationType} for {PackageId} {PackageVersion}, validation set {ValidationSetId}, {ValidationId}", packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, packageValidation.Key); var validationConfiguration = GetValidationConfiguration(packageValidation.Type); if (validationConfiguration == null) { await OnUnknownValidation(packageValidation); continue; } if (!validationConfiguration.ShouldStart) { continue; } bool prerequisitesAreMet = ArePrerequisitesMet(packageValidation, validationSet); if (!prerequisitesAreMet) { _logger.LogInformation("Prerequisites are not met for validation {ValidationType} for {PackageId} {PackageVersion}, validation set {ValidationSetId}, {ValidationId}", packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, packageValidation.Key); continue; } var validator = _validatorProvider.GetValidator(packageValidation.Type); var validationRequest = await CreateValidationRequest(packageValidation.PackageValidationSet, packageValidation); var validationResult = await validator.GetResultAsync(validationRequest); if (validationResult.Status == ValidationStatus.NotStarted) { _logger.LogInformation("Requesting validation {ValidationType} for {PackageId} {PackageVersion}, validation set {ValidationSetId}, {ValidationId}, {NupkgUrl}", packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, packageValidation.Key, validationRequest.NupkgUrl); try { validationResult = await validator.StartAsync(validationRequest); } catch (Exception e) when(validationConfiguration.FailureBehavior != ValidationFailureBehavior.MustSucceed) { // ignore exceptions for optional validators _logger.LogWarning(0, e, "Got exception while running optional validation {ValidationType} for " + "{PackageId} {PackageVersion}, validation set {ValidationSetId}, {ValidationId}, {NupkgUrl}", packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, packageValidation.Key, validationRequest.NupkgUrl); } _logger.LogInformation("Got validationStatus = {ValidationStatus} for validation {ValidationType} for {PackageId} {PackageVersion}, validation set {ValidationSetId}, {ValidationId}", validationResult.Status, packageValidation.Type, validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, packageValidation.Key); } if (validationResult.Status == ValidationStatus.NotStarted) { _logger.LogWarning("Unexpected NotStarted state after start attempt for validation {ValidationName}, package: {PackageId} {PackageVersion}", packageValidation.Type, packageValidation.PackageValidationSet.PackageId, packageValidation.PackageValidationSet.PackageNormalizedVersion); } else { await _validationStorageService.MarkValidationStartedAsync(packageValidation, validationResult); if (validationResult.Status == ValidationStatus.Succeeded || validationResult.Status == ValidationStatus.Failed) { await validator.CleanUpAsync(validationRequest); } _telemetryService.TrackValidatorStarted(validationSet.PackageId, validationSet.PackageNormalizedVersion, validationSet.ValidationTrackingId, packageValidation.Type); if (validationResult.Status == ValidationStatus.Succeeded) { UpdateStatsForValidationSuccess(processorStats, validationConfiguration); tryMoreValidations = true; } } } } return(tryMoreValidations); }