Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit f18edac

Browse files
author
Scott Bommarito
authored
Status aggregator job (#494)
1 parent e7b9e8e commit f18edac

56 files changed

Lines changed: 2747 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

NuGet.Jobs.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.Common.Job", "sr
107107
EndProject
108108
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageHash", "src\PackageHash\PackageHash.csproj", "{40843020-6F0A-48F0-AC28-42FFE3A5C21E}"
109109
EndProject
110+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatusAggregator", "src\StatusAggregator\StatusAggregator.csproj", "{D357FDB5-BF19-41A5-82B0-14C8CEC2A5EB}"
111+
EndProject
110112
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.Common.Job.Tests", "tests\Validation.Common.Job.Tests\Validation.Common.Job.Tests.csproj", "{430F63C7-20C2-4872-AC3E-DDE846E50AA4}"
111113
EndProject
112114
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.ProcessSignature", "src\Validation.PackageSigning.ProcessSignature\Validation.PackageSigning.ProcessSignature.csproj", "{DD043977-6BCD-475A-BEE2-8C34309EC622}"
@@ -135,6 +137,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Services.Revalidate",
135137
EndProject
136138
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Services.Revalidate.Tests", "tests\NuGet.Services.Revalidate.Tests\NuGet.Services.Revalidate.Tests.csproj", "{19780DCB-B307-4254-B10C-4335FC784DEA}"
137139
EndProject
140+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatusAggregator.Tests", "tests\StatusAggregator.Tests\StatusAggregator.Tests.csproj", "{784F938D-4142-4C1C-B654-0978FEAD1731}"
141+
EndProject
138142
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.Symbols.Core", "src\Validation.Symbols.Core\Validation.Symbols.Core.csproj", "{17510A22-176F-4E96-A867-E79F1B54F54F}"
139143
EndProject
140144
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monitoring.RebootSearchInstance", "src\Monitoring.RebootSearchInstance\Monitoring.RebootSearchInstance.csproj", "{ECD8DFCE-8E3C-4510-AFE3-D7EC168E8D66}"
@@ -309,6 +313,10 @@ Global
309313
{40843020-6F0A-48F0-AC28-42FFE3A5C21E}.Debug|Any CPU.Build.0 = Debug|Any CPU
310314
{40843020-6F0A-48F0-AC28-42FFE3A5C21E}.Release|Any CPU.ActiveCfg = Release|Any CPU
311315
{40843020-6F0A-48F0-AC28-42FFE3A5C21E}.Release|Any CPU.Build.0 = Release|Any CPU
316+
{D357FDB5-BF19-41A5-82B0-14C8CEC2A5EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
317+
{D357FDB5-BF19-41A5-82B0-14C8CEC2A5EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
318+
{D357FDB5-BF19-41A5-82B0-14C8CEC2A5EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
319+
{D357FDB5-BF19-41A5-82B0-14C8CEC2A5EB}.Release|Any CPU.Build.0 = Release|Any CPU
312320
{430F63C7-20C2-4872-AC3E-DDE846E50AA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
313321
{430F63C7-20C2-4872-AC3E-DDE846E50AA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
314322
{430F63C7-20C2-4872-AC3E-DDE846E50AA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -361,6 +369,10 @@ Global
361369
{19780DCB-B307-4254-B10C-4335FC784DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
362370
{19780DCB-B307-4254-B10C-4335FC784DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
363371
{19780DCB-B307-4254-B10C-4335FC784DEA}.Release|Any CPU.Build.0 = Release|Any CPU
372+
{784F938D-4142-4C1C-B654-0978FEAD1731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
373+
{784F938D-4142-4C1C-B654-0978FEAD1731}.Debug|Any CPU.Build.0 = Debug|Any CPU
374+
{784F938D-4142-4C1C-B654-0978FEAD1731}.Release|Any CPU.ActiveCfg = Release|Any CPU
375+
{784F938D-4142-4C1C-B654-0978FEAD1731}.Release|Any CPU.Build.0 = Release|Any CPU
364376
{17510A22-176F-4E96-A867-E79F1B54F54F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
365377
{17510A22-176F-4E96-A867-E79F1B54F54F}.Debug|Any CPU.Build.0 = Debug|Any CPU
366378
{17510A22-176F-4E96-A867-E79F1B54F54F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -425,6 +437,7 @@ Global
425437
{B4B7564A-965B-447B-927F-6749E2C08880} = {6A776396-02B1-475D-A104-26940ADB04AB}
426438
{FA87D075-A934-4443-8D0B-5DB32640B6D7} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
427439
{40843020-6F0A-48F0-AC28-42FFE3A5C21E} = {FA5644B5-4F08-43F6-86B3-039374312A47}
440+
{D357FDB5-BF19-41A5-82B0-14C8CEC2A5EB} = {FA5644B5-4F08-43F6-86B3-039374312A47}
428441
{430F63C7-20C2-4872-AC3E-DDE846E50AA4} = {6A776396-02B1-475D-A104-26940ADB04AB}
429442
{DD043977-6BCD-475A-BEE2-8C34309EC622} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
430443
{ED2D370C-D921-433A-A0B9-A601F936EDD3} = {FA5644B5-4F08-43F6-86B3-039374312A47}
@@ -438,6 +451,7 @@ Global
438451
{60152AB1-2EB4-4D44-B6D6-EEE24209A1F7} = {6A776396-02B1-475D-A104-26940ADB04AB}
439452
{1963909D-8BE3-4CB8-B57E-AB6A8CB22FED} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
440453
{19780DCB-B307-4254-B10C-4335FC784DEA} = {6A776396-02B1-475D-A104-26940ADB04AB}
454+
{784F938D-4142-4C1C-B654-0978FEAD1731} = {6A776396-02B1-475D-A104-26940ADB04AB}
441455
{17510A22-176F-4E96-A867-E79F1B54F54F} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
442456
{ECD8DFCE-8E3C-4510-AFE3-D7EC168E8D66} = {814F9B31-4AF3-46CC-AD61-CEB40F47083A}
443457
{21C0A0EE-8696-4013-950F-D6495D0C6E40} = {6A776396-02B1-475D-A104-26940ADB04AB}

build.ps1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Invoke-BuildStep 'Set version metadata in AssemblyInfo.cs' { `
115115
"$PSScriptRoot\src\Validation.Common.Job\Properties\AssemblyInfo.g.cs",
116116
"$PSScriptRoot\src\Validation.ScanAndSign.Core\Properties\AssemblyInfo.g.cs",
117117
"$PSScriptRoot\src\PackageLagMonitor\Properties\AssemblyInfo.g.cs",
118+
"$PSScriptRoot\src\StatusAggregator\Properties\AssemblyInfo.g.cs",
118119
"$PSScriptRoot\src\Validation.Symbols.Core\Properties\AssemblyInfo.g.cs",
119120
"$PSScriptRoot\src\Monitoring.RebootSearchInstance\Properties\AssemblyInfo.g.cs"
120121

@@ -177,7 +178,8 @@ Invoke-BuildStep 'Creating artifacts' {
177178
"src/Validation.PackageSigning.ValidateCertificate/Validation.PackageSigning.ValidateCertificate.csproj", `
178179
"src/Validation.PackageSigning.RevalidateCertificate/Validation.PackageSigning.RevalidateCertificate.csproj", `
179180
"src/PackageLagMonitor/Monitoring.PackageLag.csproj", `
180-
"src/Monitoring.RebootSearchInstance/Monitoring.RebootSearchInstance.csproj", `
181+
"src/StatusAggregator/StatusAggregator.csproj", `
182+
"src/Validation.Symbols.Core/Validation.Symbols.Core.csproj", `
181183
"src/Validation.Symbols/Validation.Symbols.csproj" `
182184
+ $ProjectsWithSymbols
183185

src/NuGet.Jobs.Common/Configuration/JobArgumentNames.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ public static class JobArgumentNames
118118
public const string MailFrom = "MailFrom";
119119
public const string SmtpUri = "SmtpUri";
120120

121+
// Arguments specific to StatusAggregator
122+
public const string StatusStorageAccount = "StatusStorageAccount";
123+
public const string StatusContainerName = "StatusContainerName";
124+
public const string StatusTableName = "StatusTableName";
125+
public const string StatusEnvironment = "StatusEnvironment";
126+
public const string StatusMaximumSeverity = "StatusMaximumSeverity";
127+
public const string StatusIncidentApiBaseUri = "StatusIncidentApiBaseUri";
128+
public const string StatusIncidentApiCertificate = "StatusIncidentApiCertificate";
129+
public const string StatusIncidentApiTeamId = "StatusIncidentApiTeamId";
130+
public const string StatusEventStartMessageDelayMinutes = "StatusEventStartMessageDelayMinutes";
131+
public const string StatusEventEndDelayMinutes = "StatusEventEndDelayMinutes";
132+
public const string StatusEventVisibilityPeriodDays = "StatusEventVisibilityPeriodDays";
133+
121134
// Arguments specific to Stats.AggregateCdnDownloadsInGallery
122135
public static string BatchSleepSeconds = "BatchSleepSeconds";
123136
}

src/NuGet.Jobs.Common/Configuration/JobConfigurationManager.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Collections.Generic;
66
using System.ComponentModel.Design;
77
using System.Linq;
8+
using Microsoft.Extensions.DependencyInjection;
89
using Microsoft.Extensions.Logging;
10+
using NuGet.Jobs.Extensions;
911
using NuGet.Services.Configuration;
1012
using NuGet.Services.KeyVault;
1113

@@ -190,7 +192,7 @@ private static Dictionary<string, string> ReadCommandLineArguments(ILogger logge
190192

191193
private static IDictionary<string, string> InjectSecrets(IServiceContainer serviceContainer, Dictionary<string, string> argsDictionary)
192194
{
193-
var secretReaderFactory = (ISecretReaderFactory)serviceContainer.GetService(typeof(ISecretReaderFactory));
195+
var secretReaderFactory = serviceContainer.GetRequiredService<ISecretReaderFactory>();
194196

195197
var secretReader = secretReaderFactory.CreateSecretReader(argsDictionary);
196198
if (secretReader == null)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.Logging;
5+
using System;
6+
7+
namespace NuGet.Jobs.Extensions
8+
{
9+
public static class LoggerExtensions
10+
{
11+
/// <summary>
12+
/// Calls <see cref="ILogger.BeginScope{TState}(TState)"/> and logs a message when entering and leaving the scope.
13+
/// </summary>
14+
public static IDisposable Scope(
15+
this ILogger logger,
16+
string message,
17+
params object[] args)
18+
{
19+
return new LoggerScopeHelper(logger, message, args);
20+
}
21+
22+
private class LoggerScopeHelper : IDisposable
23+
{
24+
private readonly ILogger _logger;
25+
private readonly IDisposable _scope;
26+
27+
private readonly string _message;
28+
private readonly object[] _args;
29+
30+
private bool _isDisposed = false;
31+
32+
public LoggerScopeHelper(
33+
ILogger logger, string message, object[] args)
34+
{
35+
_logger = logger;
36+
_message = message;
37+
_args = args;
38+
39+
_scope = logger.BeginScope(_message, _args);
40+
_logger.LogInformation("Entering scope: " + _message, _args);
41+
}
42+
43+
public void Dispose()
44+
{
45+
if (!_isDisposed)
46+
{
47+
_logger.LogInformation("Leaving scope: " + _message, _args);
48+
_scope?.Dispose(); // ILogger can return a null scope (most notably during testing with a Mock<ILogger>)
49+
_isDisposed = true;
50+
}
51+
}
52+
}
53+
}
54+
}

src/NuGet.Jobs.Common/NuGet.Jobs.Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<Compile Include="Configuration\ServiceBusConfiguration.cs" />
5050
<Compile Include="Configuration\ValidationDbConfiguration.cs" />
5151
<Compile Include="Configuration\ValidationStorageConfiguration.cs" />
52+
<Compile Include="Extensions\LoggerExtensions.cs" />
5253
<Compile Include="Extensions\SqlConnectionStringBuilderExtensions.cs" />
5354
<Compile Include="Extensions\XElementExtensions.cs" />
5455
<Compile Include="SecretReader\ISecretReaderFactory.cs" />

src/StatusAggregator/Cursor.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Logging;
7+
using NuGet.Jobs.Extensions;
8+
using NuGet.Services.Status.Table;
9+
using StatusAggregator.Table;
10+
11+
namespace StatusAggregator
12+
{
13+
public class Cursor : ICursor
14+
{
15+
public Cursor(
16+
ITableWrapper table,
17+
ILogger<Cursor> logger)
18+
{
19+
_table = table ?? throw new ArgumentNullException(nameof(table));
20+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
21+
}
22+
23+
private readonly ITableWrapper _table;
24+
25+
private readonly ILogger<Cursor> _logger;
26+
27+
public async Task<DateTime> Get()
28+
{
29+
using (_logger.Scope("Fetching cursor."))
30+
{
31+
var cursor = await _table.Retrieve<CursorEntity>(
32+
CursorEntity.DefaultPartitionKey, CursorEntity.DefaultRowKey);
33+
34+
DateTime value;
35+
if (cursor == null)
36+
{
37+
// If we can't find a cursor, the job is likely uninitialized, so start at the beginning of time.
38+
value = DateTime.MinValue;
39+
_logger.LogInformation("Could not fetch cursor, reinitializing cursor at {Cursor}.", value);
40+
}
41+
else
42+
{
43+
value = cursor.Value;
44+
_logger.LogInformation("Fetched cursor with value {Cursor}.", value);
45+
}
46+
47+
return value;
48+
}
49+
}
50+
51+
public Task Set(DateTime value)
52+
{
53+
using (_logger.Scope("Updating cursor to {Cursor}.", value))
54+
{
55+
var cursorEntity = new CursorEntity(value);
56+
return _table.InsertOrReplaceAsync(cursorEntity);
57+
}
58+
}
59+
}
60+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Logging;
8+
using NuGet.Jobs.Extensions;
9+
using NuGet.Services.Status.Table;
10+
using StatusAggregator.Table;
11+
12+
namespace StatusAggregator
13+
{
14+
public class EventUpdater : IEventUpdater
15+
{
16+
public readonly TimeSpan _eventEndDelay;
17+
18+
private readonly ITableWrapper _table;
19+
private readonly IMessageUpdater _messageUpdater;
20+
21+
private readonly ILogger<EventUpdater> _logger;
22+
23+
public EventUpdater(
24+
ITableWrapper table,
25+
IMessageUpdater messageUpdater,
26+
StatusAggregatorConfiguration configuration,
27+
ILogger<EventUpdater> logger)
28+
{
29+
_table = table ?? throw new ArgumentNullException(nameof(table));
30+
_messageUpdater = messageUpdater ?? throw new ArgumentNullException(nameof(messageUpdater));
31+
_eventEndDelay = TimeSpan.FromMinutes(configuration?.EventEndDelayMinutes ?? throw new ArgumentNullException(nameof(configuration)));
32+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
33+
}
34+
35+
public async Task UpdateActiveEvents(DateTime cursor)
36+
{
37+
using (_logger.Scope("Updating active events."))
38+
{
39+
var activeEvents = _table.GetActiveEvents().ToList();
40+
_logger.LogInformation("Updating {ActiveEventsCount} active events.", activeEvents.Count());
41+
foreach (var activeEvent in activeEvents)
42+
{
43+
await UpdateEvent(activeEvent, cursor);
44+
}
45+
}
46+
}
47+
48+
public async Task<bool> UpdateEvent(EventEntity eventEntity, DateTime cursor)
49+
{
50+
eventEntity = eventEntity ?? throw new ArgumentNullException(nameof(eventEntity));
51+
52+
using (_logger.Scope("Updating event '{EventRowKey}' given cursor {Cursor}.", eventEntity.RowKey, cursor))
53+
{
54+
if (!eventEntity.IsActive)
55+
{
56+
_logger.LogInformation("Event is inactive, cannot update.");
57+
return false;
58+
}
59+
60+
var incidentsLinkedToEventQuery = _table.GetIncidentsLinkedToEvent(eventEntity);
61+
62+
var incidentsLinkedToEvent = incidentsLinkedToEventQuery.ToList();
63+
if (!incidentsLinkedToEvent.Any())
64+
{
65+
_logger.LogInformation("Event has no linked incidents and must have been created manually, cannot update.");
66+
return false;
67+
}
68+
69+
var shouldDeactivate = !incidentsLinkedToEventQuery
70+
.Where(i => i.IsActive || i.MitigationTime > cursor - _eventEndDelay)
71+
.ToList()
72+
.Any();
73+
74+
if (shouldDeactivate)
75+
{
76+
_logger.LogInformation("Deactivating event because its incidents are inactive and too old.");
77+
var mitigationTime = incidentsLinkedToEvent
78+
.Max(i => i.MitigationTime ?? DateTime.MinValue);
79+
eventEntity.EndTime = mitigationTime;
80+
81+
await _messageUpdater.CreateMessageForEventStart(eventEntity, mitigationTime);
82+
await _messageUpdater.CreateMessageForEventEnd(eventEntity);
83+
84+
// Update the event
85+
await _table.InsertOrReplaceAsync(eventEntity);
86+
}
87+
else
88+
{
89+
_logger.LogInformation("Event has active or recent incidents so it will not be deactivated.");
90+
await _messageUpdater.CreateMessageForEventStart(eventEntity, cursor);
91+
}
92+
93+
return shouldDeactivate;
94+
}
95+
}
96+
}
97+
}

src/StatusAggregator/ICursor.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace StatusAggregator
8+
{
9+
/// <summary>
10+
/// Maintains the current progress of the job.
11+
/// </summary>
12+
public interface ICursor
13+
{
14+
Task<DateTime> Get();
15+
Task Set(DateTime value);
16+
}
17+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using NuGet.Services.Status.Table;
7+
8+
namespace StatusAggregator
9+
{
10+
/// <summary>
11+
/// Handles updating any active <see cref="EventEntity"/>s.
12+
/// </summary>
13+
public interface IEventUpdater
14+
{
15+
/// <summary>
16+
/// Updates all active <see cref="EventEntity"/>s.
17+
/// </summary>
18+
/// <param name="cursor">The current timestamp processed by the job.</param>
19+
Task UpdateActiveEvents(DateTime cursor);
20+
21+
/// <summary>
22+
/// Update <paramref name="eventEntity"/> given <paramref name="cursor"/>.
23+
/// Determines whether or not to deactivate <paramref name="eventEntity"/> and updates any messages associated with the event.
24+
/// </summary>
25+
/// <param name="cursor">The current timestamp processed by the job.</param>
26+
/// <returns>Whether or not <paramref name="eventEntity"/> was deactivated.</returns>
27+
Task<bool> UpdateEvent(EventEntity eventEntity, DateTime cursor);
28+
}
29+
}

0 commit comments

Comments
 (0)