Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.
/ NuGet.Jobs Public archive

Commit

Permalink
Merge pull request #363 from NuGet/dev
Browse files Browse the repository at this point in the history
[ReleasePrep][2018.03.08]FI of master into dev
  • Loading branch information
loic-sharma authored Mar 8, 2018
2 parents 57315b9 + 417240f commit 5a85bee
Show file tree
Hide file tree
Showing 38 changed files with 1,638 additions and 773 deletions.
4 changes: 4 additions & 0 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ trap {
if (-not (Test-Path "$PSScriptRoot/build")) {
New-Item -Path "$PSScriptRoot/build" -ItemType "directory"
}

# Enable TLS 1.2 since GitHub requires it.
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

wget -UseBasicParsing -Uri "https://raw.githubusercontent.com/NuGet/ServerCommon/$BuildBranch/build/init.ps1" -OutFile "$PSScriptRoot/build/init.ps1"
. "$PSScriptRoot/build/init.ps1" -BuildBranch "$BuildBranch"

Expand Down
15 changes: 15 additions & 0 deletions src/NuGet.Services.Validation.Orchestrator/BaseValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;

namespace NuGet.Services.Validation.Orchestrator
{
public abstract class BaseValidator
{
public virtual Task CleanUpAsync(IValidationRequest request)
{
return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Extensions.Options;

namespace NuGet.Services.Validation.Orchestrator
{
/// <summary>
/// Provides a methods for checking configuration validity
/// </summary>
public class ConfigurationValidator
{
private readonly IValidatorProvider _validatorProvider;
private readonly ValidationConfiguration _configuration;

public ConfigurationValidator(
IValidatorProvider validatorProvider,
IOptionsSnapshot<ValidationConfiguration> optionsAccessor)
{
if (optionsAccessor == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
}

_validatorProvider = validatorProvider ?? throw new ArgumentNullException(nameof(validatorProvider));
_configuration = optionsAccessor.Value ?? throw new ArgumentException("Value property cannot be null", nameof(optionsAccessor));
}

/// <summary>
/// Checks if configuration object is valid
/// </summary>
public void Validate()
{
CheckValidationsNumber();

CheckPropertyValues();

CheckDuplicateValidations();

CheckUnknownPrerequisites();

CheckUnknownValidators();

CheckForCyclesAndParallelProcessors();

CheckForUnrunnableRequiredValidations();
}

private void CheckValidationsNumber()
{
if (_configuration.Validations == null || !_configuration.Validations.Any())
{
throw new ConfigurationErrorsException("Must have at least one validation declared");
}
}

private void CheckPropertyValues()
{
foreach (var validationConfigurationItem in _configuration.Validations)
{
if (string.IsNullOrWhiteSpace(validationConfigurationItem.Name))
{
throw new ConfigurationErrorsException("Validation name cannot be empty");
}

if (validationConfigurationItem.FailAfter == TimeSpan.Zero)
{
throw new ConfigurationErrorsException($"failAfter timeout must be set for validation {validationConfigurationItem.Name}");
}
}
}

private void CheckDuplicateValidations()
{
var duplicateValidations = _configuration.Validations
.Select(v => v.Name)
.GroupBy(n => n)
.Where(g => g.Count() > 1)
.ToList();
if (duplicateValidations.Any())
{
throw new ConfigurationErrorsException($"Duplicate validations: {string.Join(", ", duplicateValidations.Select(d => d.Key))}");
}
}

private void CheckUnknownPrerequisites()
{
var declaredValidations = new HashSet<string>(_configuration.Validations.Select(v => v.Name));
var prerequisites = new HashSet<string>(_configuration.Validations.Select(v => v.RequiredValidations).SelectMany(p => p));
prerequisites.ExceptWith(declaredValidations);
if (prerequisites.Any())
{
throw new ConfigurationErrorsException($"Unknown validations set as prerequisites: {string.Join(", ", prerequisites)}");
}
}

private void CheckUnknownValidators()
{
foreach (var validatorItem in _configuration.Validations)
{
// This method will throw if the validator does not exist.
var validatorType = _validatorProvider.GetValidatorType(validatorItem.Name);
if (validatorType == null)
{
throw new ConfigurationErrorsException("Validator implementation not found for " + validatorItem.Name);
}
}
}

private void CheckForUnrunnableRequiredValidations()
{
// checks for the case when validation that must run depends on a validation that
// is configured not to run
// we'll just walk up the dependency chain of each runnable validation and look for
// not runnable validations

var validations = _configuration.Validations.ToDictionary(v => v.Name);
var runnableValidations = _configuration.Validations.Where(v => v.ShouldStart);

foreach (var validation in runnableValidations)
{
var checkQueue = new Queue<string>(validation.RequiredValidations);
while (checkQueue.Any())
{
var requiredValidationName = checkQueue.Dequeue();
var requiredValidation = validations[requiredValidationName];
if (!requiredValidation.ShouldStart)
{
throw new ConfigurationErrorsException($"Runnable validation {validation.Name} cannot be run because it requires non-runnable validation {requiredValidationName} to complete before it can be started.");
}
requiredValidation.RequiredValidations.ForEach(checkQueue.Enqueue);
}
}
}

private void CheckForCyclesAndParallelProcessors()
{
var processorNames = _configuration
.Validations
.Select(x => x.Name)
.Where(x => typeof(IProcessor).IsAssignableFrom(_validatorProvider.GetValidatorType(x)))
.ToList();

TopologicalSort.Validate(_configuration.Validations, processorNames);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;

namespace NuGet.Services.Validation.Orchestrator
{
public static class TopologicalSort
{
/// <summary>
/// Processors cannot run in parallel with other processors or even with other validators. Suppose you have the
/// following validator graph (where --> indicates a validator dependent)
///
/// ---> Validator B ---
/// / \
/// Validator A --- ---> Validator D
/// \ /
/// ---> Validator C ---
///
/// In this case, B and C cannot be processors. A and D can. Given a graph of validator dependencies and the
/// list of validators that are processors, we use the following algorithm to determine whether it is possible
/// for a processor to run in parallel with anything else.
///
/// 1. Enumerate all valid orderings using topological sort. In our example above, this would be:
/// - A B C D
/// - A C B D
/// 2. For each processor, verify that the position in all orderings is the same.
/// - Note that A is always first and D is always fourth.
///
/// This allows us to verify that a validator configuration is safe before orchestrator even starts accepting
/// validation messages.
/// </summary>
/// <param name="validators">The validator configuration items.</param>
/// <param name="cannotBeParallel">The names of validators that are also processors.</param>
/// <exception cref="ConfigurationErrorsException">
/// Thrown if a cycle or parallel processor is found
/// </exception>
public static void Validate(IReadOnlyList<ValidationConfigurationItem> validators, IReadOnlyList<string> cannotBeParallel)
{
var allOrders = EnumerateAll(validators);
if (!allOrders.Any())
{
throw new ConfigurationErrorsException("No validation sequences were found. This indicates a cycle in the validation dependencies.");
}

// A dictionary mapping the name of the validator to its index in the first topological sort result. All
// other results must have their processors at the same indexes. If this is true, that means that no
// validators or processors can run in parallel with any processor.
var nameToExpectedIndex = allOrders[0]
.Select((x, i) => new { Name = x, Index = i })
.ToDictionary(x => x.Name, x => x.Index);

foreach (var order in allOrders.Skip(1))
{
foreach (var name in cannotBeParallel)
{
var index = nameToExpectedIndex[name];
var otherName = order[index];
if (otherName != name)
{
throw new ConfigurationErrorsException(
$"The processor {name} could run in parallel with {otherName}. Processors must not run " +
$"in parallel with any other validators.");
}
}
}
}

public static List<List<string>> EnumerateAll(IReadOnlyList<ValidationConfigurationItem> validators)
{
// Build the graph.
var graph = validators.ToDictionary(x => x.Name, x => new ValidatorNode(x.Name));

// Invert the node relationship. Validators specify what they depend on. Nodes in a directed graph to be
// explored using topological sort should specify what their dependents are.
foreach (var validator in validators)
{
foreach (var dependencyName in validator.RequiredValidations)
{
graph[validator.Name].InDegree++;
graph[dependencyName].DependentValidations.Add(validator.Name);
}
}

// Enumerate all combindations.
var allResults = new List<List<string>>();
AllTopologicalSort(graph, new List<string>(), allResults);

return allResults;
}

/// <summary>
/// Executes topological sort on the provided graph of validators. All possible results are enumerated and
/// returned.
/// </summary>
/// <remarks>
/// Source: https://www.geeksforgeeks.org/all-topological-sorts-of-a-directed-acyclic-graph/
/// </remarks>
private static void AllTopologicalSort(
IReadOnlyDictionary<string, ValidatorNode> graph,
List<string> currentResult,
List<List<string>> allResults)
{
var done = false;

foreach (var node in graph.Values)
{
if (node.InDegree == 0 && !node.Visited)
{
foreach (var dependencyName in node.DependentValidations)
{
graph[dependencyName].InDegree--;
}

currentResult.Add(node.Name);
node.Visited = true;

// Recurse.
AllTopologicalSort(graph, currentResult, allResults);

node.Visited = false;
currentResult.RemoveAt(currentResult.Count - 1);

foreach (var dependencyName in node.DependentValidations)
{
graph[dependencyName].InDegree++;
}

done = true;
}
}

if (!done && currentResult.Count == graph.Count)
{
// Append a copy of the running result.
allResults.Add(currentResult.ToList());
}
}

private class ValidatorNode
{
public ValidatorNode(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}

public string Name { get; }
public List<string> DependentValidations { get; } = new List<string>();
public int InDegree { get; set; }
public bool Visited { get; set; }
}
}
}
Loading

0 comments on commit 5a85bee

Please sign in to comment.