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

[Availability] Update StatusAggregator to support new AI availability test alerts #778

Merged
merged 7 commits into from
Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/StatusAggregator/Export/ComponentExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ public IComponent Export()

if (currentComponent == null)
{
throw new InvalidOperationException($"Couldn't find component with path {activeEntity.AffectedComponentPath} corresponding to active entities.");
_logger.LogWarning(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the past, all component paths were specified by the job, and if a component path was specified that did not exist, the process would crash.

Because I'm adding an alert that specifies its own component path, I felt it was best to remove this protection. The StatusAggregator job would be essentially permanently stuck if it encountered a malformed AI alert. This way, the process will gracefully ignore any non-existent component paths.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good move.

"Couldn't find component with path {AffectedComponentPath} corresponding to active entities. Ignoring entity.",
activeEntity.AffectedComponentPath);

continue;
}

currentComponent.Status = (ComponentStatus)activeEntity.AffectedComponentStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using NuGet.Services.Incidents;
using NuGet.Services.Status;

namespace StatusAggregator.Parse
{
public class AIAvailabilityIncidentRegexParsingHandler : EnvironmentPrefixIncidentRegexParsingHandler
{
public const string TestGroupName = "Test";
public const string AffectedComponentPathGroupName = "AffectedComponentPath";
private static string SubtitleRegEx = $@"AI Availability test '(?<{TestGroupName}>.+)' is failing!( \((?<{AffectedComponentPathGroupName}>.+)\))?";

private readonly ILogger<AIAvailabilityIncidentRegexParsingHandler> _logger;

public AIAvailabilityIncidentRegexParsingHandler(
IEnumerable<IIncidentRegexParsingFilter> filters,
ILogger<AIAvailabilityIncidentRegexParsingHandler> logger)
: base(SubtitleRegEx, filters)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public override bool TryParseAffectedComponentPath(Incident incident, GroupCollection groups, out string affectedComponentPath)
{
var test = groups[TestGroupName].Value;
affectedComponentPath = groups[AffectedComponentPathGroupName].Value;
var environment = groups[EnvironmentRegexParsingFilter.EnvironmentGroupName].Value;
_logger.LogInformation("Test is named {Test} and affects {AffectedComponentPath} in the {Environment} environment.", test, affectedComponentPath, environment);
return !string.IsNullOrEmpty(affectedComponentPath);
}

public override bool TryParseAffectedComponentStatus(Incident incident, GroupCollection groups, out ComponentStatus affectedComponentStatus)
{
affectedComponentStatus = ComponentStatus.Down;
joelverhagen marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public EnvironmentPrefixIncidentRegexParsingHandler(

private static string PrependEnvironmentRegexGroup(string subtitleRegEx)
{
return $@"\[(?<{EnvironmentRegexParsingFilter.EnvironmentGroupName}>.*)\] {subtitleRegEx}";
return $@"\[(?<{EnvironmentRegexParsingFilter.EnvironmentGroupName}>.+)\] {subtitleRegEx}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class PingdomIncidentRegexParsingHandler : IncidentRegexParsingHandler
{
public const string CheckNameGroupName = "CheckName";
public const string CheckUrlGroupName = "CheckUrl";
private static string SubtitleRegEx = $@"Pingdom check '(?<{CheckNameGroupName}>.*)' is failing! '(?<{CheckUrlGroupName}>.*)' is DOWN!";
private static string SubtitleRegEx = $@"Pingdom check '(?<{CheckNameGroupName}>.+)' is failing! '(?<{CheckUrlGroupName}>.+)' is DOWN!";

private readonly ILogger<PingdomIncidentRegexParsingHandler> _logger;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class TrafficManagerEndpointStatusIncidentRegexParsingHandler : Environme
{
public const string DomainGroupName = "Domain";
public const string TargetGroupName = "Target";
private static string SubtitleRegEx = $"Traffic Manager for (?<{DomainGroupName}>.*) is reporting (?<{TargetGroupName}>.*) as not Online!";
private static string SubtitleRegEx = $"Traffic Manager for (?<{DomainGroupName}>.+) is reporting (?<{TargetGroupName}>.+) as not Online!";

private readonly ILogger<TrafficManagerEndpointStatusIncidentRegexParsingHandler> _logger;

Expand Down
1 change: 1 addition & 0 deletions src/StatusAggregator/StatusAggregator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<Compile Include="Messages\ExistingStartMessageContext.cs" />
<Compile Include="Parse\IIncidentRegexParsingHandler.cs" />
<Compile Include="Parse\IncidentRegexParsingHandler.cs" />
<Compile Include="Parse\AIAvailabilityIncidentRegexParsingHandler.cs" />
<Compile Include="Update\EventMessagingUpdater.cs" />
<Compile Include="Messages\IIncidentGroupMessageFilter.cs" />
<Compile Include="Messages\IMessageChangeEventIterator.cs" />
Expand Down
35 changes: 17 additions & 18 deletions tests/StatusAggregator.Tests/Export/ComponentExporterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,6 @@ public void ReturnsUnaffectedComponentTreeWithNoEntities()
c => Assert.Equal(ComponentStatus.Up, c.Status));
}

[Fact]
public void ThrowsWithMissingPath()
{
var eventWithMessage = new EventEntity(Level2A.Path, DefaultStartTime, ComponentStatus.Degraded);
var messageForEventWithMessage = new MessageEntity(eventWithMessage, DefaultStartTime, "", MessageType.Manual);
var missingPathIncidentGroupForEventWithMessage = new IncidentGroupEntity(eventWithMessage, "missingPath", ComponentStatus.Degraded, DefaultStartTime);

Table.SetupQuery(messageForEventWithMessage);
Table.SetupQuery(missingPathIncidentGroupForEventWithMessage);
Table.SetupQuery(eventWithMessage);

Assert.Throws<InvalidOperationException>(() => Exporter.Export());
}

[Fact]
public void AppliesActiveEntitiesToComponentTree()
{
Expand All @@ -56,6 +42,7 @@ public void AppliesActiveEntitiesToComponentTree()
var downIncidentGroupForEventWithMessage = new IncidentGroupEntity(eventWithMessage, Level3AFrom2A.Path, ComponentStatus.Down, DefaultStartTime);
var upIncidentGroupForEventWithMessage = new IncidentGroupEntity(eventWithMessage, Level3AFrom2A.Path, ComponentStatus.Up, DefaultStartTime);
var inactiveIncidentGroupForEventWithMessage = new IncidentGroupEntity(eventWithMessage, Level3BFrom2A.Path, ComponentStatus.Degraded, DefaultStartTime, DefaultStartTime);
var missingPathIncidentGroupForEventWithMessage = new IncidentGroupEntity(eventWithMessage, "missingPath", ComponentStatus.Degraded, DefaultStartTime);

var eventWithoutMessage = new EventEntity(Level2B.Path, DefaultStartTime, ComponentStatus.Degraded);
var incidentGroupForEventWithoutMessage = new IncidentGroupEntity(eventWithoutMessage, Level3AFrom2B.Path, ComponentStatus.Degraded, DefaultStartTime);
Expand All @@ -64,11 +51,23 @@ public void AppliesActiveEntitiesToComponentTree()
var messageForInactiveEventWithMessage = new MessageEntity(inactiveEventWithMessage, DefaultStartTime + TimeSpan.FromDays(1), "", MessageType.Manual);
var incidentGroupForInactiveEventWithMessage = new IncidentGroupEntity(inactiveEventWithMessage, Level3BFrom2B.Path, ComponentStatus.Degraded, DefaultStartTime + TimeSpan.FromDays(1));

Table.SetupQuery(messageForEventWithMessage, messageForInactiveEventWithMessage);
Table.SetupQuery(
degradedIncidentGroupForEventWithMessage, downIncidentGroupForEventWithMessage, upIncidentGroupForEventWithMessage, inactiveIncidentGroupForEventWithMessage,
incidentGroupForEventWithoutMessage, incidentGroupForInactiveEventWithMessage);
Table.SetupQuery(eventWithMessage, eventWithoutMessage, inactiveEventWithMessage);
messageForEventWithMessage,
messageForInactiveEventWithMessage);

Table.SetupQuery(
degradedIncidentGroupForEventWithMessage,
downIncidentGroupForEventWithMessage,
upIncidentGroupForEventWithMessage,
inactiveIncidentGroupForEventWithMessage,
missingPathIncidentGroupForEventWithMessage,
incidentGroupForEventWithoutMessage,
incidentGroupForInactiveEventWithMessage);

Table.SetupQuery(
eventWithMessage,
eventWithoutMessage,
inactiveEventWithMessage);

var result = Exporter.Export();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Moq;
using NuGet.Services.Incidents;
using NuGet.Services.Status;
using StatusAggregator.Parse;
using Xunit;
using Match = System.Text.RegularExpressions.Match;

namespace StatusAggregator.Tests.Parse
{
public class AIAvailabilityIncidentRegexParsingHandlerTests
{
public class TheTryParseAffectedComponentPathMethod : AIAvailabilityIncidentRegexParsingHandlerTest
{
scottbommarito marked this conversation as resolved.
Show resolved Hide resolved
[Theory]
[InlineData("blah blah blah blah", false, "")]
[InlineData("[env] AI Availability test 'test' is failing!", false, "")]
[InlineData("[env] AI Availability test '' is failing! (path)", false, "")]
[InlineData("[env] AI Availability test 'test' is failing! ()", false, "")]
[InlineData("[env] AI Availability test 'test' is failing! (path)", true, "path")]
public void ReturnsExpectedResponse(string title, bool success, string affectedComponentPath)
{
var incident = new Incident { Title = title };
IncidentParsingHandlerTestUtility.AssertTryParseAffectedComponentPath(
Handler, incident, success, affectedComponentPath);
}
}

public class TheTryParseAffectedComponentStatusMethod : AIAvailabilityIncidentRegexParsingHandlerTest
{
[Fact]
public void ReturnsExpectedResponse()
{
var result = Handler.TryParseAffectedComponentStatus(new Incident(), Match.Empty.Groups, out var status);
Assert.True(result);
Assert.Equal(ComponentStatus.Down, status);
}
}

public class AIAvailabilityIncidentRegexParsingHandlerTest
{
public string Environment = "env";
public AIAvailabilityIncidentRegexParsingHandler Handler { get; }

public AIAvailabilityIncidentRegexParsingHandlerTest()
{
Handler = Construct(
new[] { IncidentParsingHandlerTestUtility.CreateEnvironmentFilter(Environment) });
}
}

public class TheConstructor
: EnvironmentPrefixIncidentRegexParsingHandlerTests.TheConstructor<AIAvailabilityIncidentRegexParsingHandler>
{
protected override AIAvailabilityIncidentRegexParsingHandler Construct(IEnumerable<IIncidentRegexParsingFilter> filters)
{
return AIAvailabilityIncidentRegexParsingHandlerTests.Construct(filters.ToArray());
}
}

public static AIAvailabilityIncidentRegexParsingHandler Construct(params IIncidentRegexParsingFilter[] filters)
{
return new AIAvailabilityIncidentRegexParsingHandler(
filters,
Mock.Of<ILogger<AIAvailabilityIncidentRegexParsingHandler>>());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void ThrowsWithoutEnvironmentRegexFilter()
[Fact]
public void DoesNotThrowWithEnvironmentFilter()
{
var handler = Construct(new[] { ParsingUtility.CreateEnvironmentFilter() });
var handler = Construct(new[] { IncidentParsingHandlerTestUtility.CreateEnvironmentFilter() });
Assert.NotNull(handler);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Text.RegularExpressions;
using NuGet.Services.Incidents;
using StatusAggregator.Parse;
using Xunit;
Expand Down Expand Up @@ -47,7 +48,7 @@ public void ReturnsTrueIfCorrectEnvironment(string environment)

private static Match GetMatchWithEnvironmentGroup(string environment)
{
return ParsingUtility.GetMatchWithGroup(EnvironmentRegexParsingFilter.EnvironmentGroupName, environment);
return Regex.Match($"[{environment}]", $@"\[(?<{ EnvironmentRegexParsingFilter.EnvironmentGroupName}>.*)\]");
}
}

Expand All @@ -62,7 +63,7 @@ public class EnvironmentRegexParsingFilterTest

public EnvironmentRegexParsingFilterTest()
{
Filter = ParsingUtility.CreateEnvironmentFilter(Environment1, Environment2);
Filter = IncidentParsingHandlerTestUtility.CreateEnvironmentFilter(Environment1, Environment2);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Moq;
using NuGet.Services.Incidents;
using NuGet.Services.Status;
using StatusAggregator.Parse;
using Xunit;
using Match = System.Text.RegularExpressions.Match;

namespace StatusAggregator.Tests.Parse
{
public static class IncidentParsingHandlerTestUtility
{
public static EnvironmentRegexParsingFilter CreateEnvironmentFilter(params string[] environments)
{
var config = new StatusAggregatorConfiguration
{
Environments = environments
};

return new EnvironmentRegexParsingFilter(
config,
Mock.Of<ILogger<EnvironmentRegexParsingFilter>>());
}

public static SeverityRegexParsingFilter CreateSeverityFilter(int severity)
{
var config = new StatusAggregatorConfiguration
{
MaximumSeverity = severity
};

return new SeverityRegexParsingFilter(
config,
Mock.Of<ILogger<SeverityRegexParsingFilter>>());
}

public static void AssertTryParseAffectedComponentPath(
IIncidentRegexParsingHandler handler,
Incident incident,
bool success,
string expectedAffectedComponentPath = null)
{
var affectedComponentPath = string.Empty;
var result =
TryGetMatch(incident.Title, handler.RegexPattern, out var match) &&
handler.TryParseAffectedComponentPath(incident, match.Groups, out affectedComponentPath);

Assert.Equal(success, result);
if (!result)
{
return;
}

Assert.Equal(expectedAffectedComponentPath, affectedComponentPath);
}

public static void AssertTryParseAffectedComponentStatus(
IIncidentRegexParsingHandler handler,
Incident incident,
bool success,
ComponentStatus expectedAffectedComponentStatus = ComponentStatus.Up)
{
var affectedComponentStatus = ComponentStatus.Up;
var result =
TryGetMatch(incident.Title, handler.RegexPattern, out var match) &&
handler.TryParseAffectedComponentStatus(incident, match.Groups, out affectedComponentStatus);

Assert.Equal(success, result);
if (!result)
{
return;
}

Assert.Equal(expectedAffectedComponentStatus, affectedComponentStatus);
}

private static bool TryGetMatch(string title, string pattern, out Match match)
{
match = null;
try
{
match = Regex.Match(title, pattern, RegexOptions.None, TimeSpan.FromSeconds(5));
}
catch
{
return false;
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class OutdatedSearchServiceInstanceIncidentRegexParsingHandlerTest

public OutdatedSearchServiceInstanceIncidentRegexParsingHandlerTest()
{
var environmentFilter = ParsingUtility.CreateEnvironmentFilter();
var environmentFilter = IncidentParsingHandlerTestUtility.CreateEnvironmentFilter();
Handler = Construct(new[] { environmentFilter });
}
}
Expand All @@ -67,8 +67,8 @@ public class TheConstructor
[Fact]
public void IgnoresSeverityFilter()
{
var severityFilter = ParsingUtility.CreateSeverityFilter(0);
var environmentFilter = ParsingUtility.CreateEnvironmentFilter();
var severityFilter = IncidentParsingHandlerTestUtility.CreateSeverityFilter(0);
var environmentFilter = IncidentParsingHandlerTestUtility.CreateEnvironmentFilter();

var handler = Construct(new IIncidentRegexParsingFilter[] { severityFilter, environmentFilter });

Expand Down
Loading