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

Commit 0bb6322

Browse files
authored
Package Lag Monitor (#433)
Adding Package Lag Monitoring Job. * Adding package lag base files. * Queueu to service bus queue. * Adding config for everything. Pull from Azure. * Add PackageLagMessage * Package Ref, DI, JsonConfig, rename namespace * Addng a missed file in namespace rename. * Make multiple regions log from one instance. * Updating configs * Update names in assembly info * Fixing a binding redirect. * Adding Sign target to csproj * Remove unused variable * Update build.ps1 to include new package. * Adding mssing nuspec to project. * Rename telemetryService -> PackageLagTelemetryService. Add max retry for each thread. update compare logic to use lastedited timestamp. fix logging format. * Loggng changes. Clearer delete propogation. * Update to new version of protocol.catalog * Updating refernce to protocol.catalog again. * Fix UTC correction that's not needed for deployed. * Better log tracking. WaitPoll -> timespan, max retries for catalog, abandon if fail commit parse. formatting. * Update to new Init signature for JobBase.
1 parent e030495 commit 0bb6322

24 files changed

Lines changed: 1055 additions & 2 deletions

NuGet.Jobs.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.R
121121
EndProject
122122
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Stats.AggregateCdnDownloadsInGallery", "tests\Tests.Stats.AggregateCdnDownloadsInGallery\Tests.Stats.AggregateCdnDownloadsInGallery.csproj", "{136411AF-B9FA-438D-B790-9FB78A5F7F54}"
123123
EndProject
124+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Monitoring", "Monitoring", "{814F9B31-4AF3-46CC-AD61-CEB40F47083A}"
125+
EndProject
126+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monitoring.PackageLag", "src\PackageLagMonitor\Monitoring.PackageLag.csproj", "{B5147169-E941-4CF8-9FCD-1C123ACD3149}"
127+
EndProject
124128
Global
125129
GlobalSection(SolutionConfigurationPlatforms) = preSolution
126130
Debug|Any CPU = Debug|Any CPU
@@ -313,6 +317,10 @@ Global
313317
{136411AF-B9FA-438D-B790-9FB78A5F7F54}.Debug|Any CPU.Build.0 = Debug|Any CPU
314318
{136411AF-B9FA-438D-B790-9FB78A5F7F54}.Release|Any CPU.ActiveCfg = Release|Any CPU
315319
{136411AF-B9FA-438D-B790-9FB78A5F7F54}.Release|Any CPU.Build.0 = Release|Any CPU
320+
{B5147169-E941-4CF8-9FCD-1C123ACD3149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
321+
{B5147169-E941-4CF8-9FCD-1C123ACD3149}.Debug|Any CPU.Build.0 = Debug|Any CPU
322+
{B5147169-E941-4CF8-9FCD-1C123ACD3149}.Release|Any CPU.ActiveCfg = Release|Any CPU
323+
{B5147169-E941-4CF8-9FCD-1C123ACD3149}.Release|Any CPU.Build.0 = Release|Any CPU
316324
EndGlobalSection
317325
GlobalSection(SolutionProperties) = preSolution
318326
HideSolutionNode = FALSE
@@ -364,6 +372,7 @@ Global
364372
{EA32E1E5-7E7D-44E6-B496-43E1FEDE9400} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
365373
{64095857-E9E3-4D9C-8769-7E558CD757CB} = {6A776396-02B1-475D-A104-26940ADB04AB}
366374
{136411AF-B9FA-438D-B790-9FB78A5F7F54} = {6A776396-02B1-475D-A104-26940ADB04AB}
375+
{B5147169-E941-4CF8-9FCD-1C123ACD3149} = {814F9B31-4AF3-46CC-AD61-CEB40F47083A}
367376
EndGlobalSection
368377
GlobalSection(ExtensibilityGlobals) = postSolution
369378
SolutionGuid = {284A7AC3-FB43-4F1F-9C9C-2AF0E1F46C2B}

build.ps1

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ Invoke-BuildStep 'Set version metadata in AssemblyInfo.cs' { `
111111
"$PSScriptRoot\src\Validation.PackageSigning.ValidateCertificate\Properties\AssemblyInfo.g.cs",
112112
"$PSScriptRoot\src\Validation.PackageSigning.RevalidateCertificate\Properties\AssemblyInfo.g.cs",
113113
"$PSScriptRoot\src\NuGet.Jobs.Common\Properties\AssemblyInfo.g.cs",
114-
"$PSScriptRoot\src\Validation.Common.Job\Properties\AssemblyInfo.g.cs"
114+
"$PSScriptRoot\src\Validation.Common.Job\Properties\AssemblyInfo.g.cs",
115+
"$PSScriptRoot\src\PackageLagMonitor\Properties\AssemblyInfo.g.cs"
115116

116117
$versionMetadata | ForEach-Object {
117118
Set-VersionInfo -Path $_ -Version $SimpleVersion -Branch $Branch -Commit $CommitSHA
@@ -167,7 +168,8 @@ Invoke-BuildStep 'Creating artifacts' {
167168
"src/Stats.CollectAzureChinaCDNLogs/Stats.CollectAzureChinaCDNLogs.csproj", `
168169
"src/Validation.PackageSigning.ProcessSignature/Validation.PackageSigning.ProcessSignature.csproj", `
169170
"src/Validation.PackageSigning.ValidateCertificate/Validation.PackageSigning.ValidateCertificate.csproj", `
170-
"src/Validation.PackageSigning.RevalidateCertificate/Validation.PackageSigning.RevalidateCertificate.csproj" `
171+
"src/Validation.PackageSigning.RevalidateCertificate/Validation.PackageSigning.RevalidateCertificate.csproj", `
172+
"src/PackageLagMonitor/Monitoring.PackageLag.csproj" `
171173
+ $ProjectsWithSymbols
172174

173175
Foreach ($Project in $Projects) {

src/PackageLagMonitor/App.config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
5+
</startup>
6+
</configuration>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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 NuGet.Services.AzureManagement;
5+
6+
namespace NuGet.Jobs.Montoring.PackageLag
7+
{
8+
public class AzureManagementAPIWrapperConfiguration : IAzureManagementAPIWrapperConfiguration
9+
{
10+
public string ClientId { get; set; }
11+
12+
public string ClientSecret { get; set; }
13+
}
14+
}

src/PackageLagMonitor/Instance.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
namespace NuGet.Jobs.Montoring.PackageLag
5+
{
6+
public class Instance
7+
{
8+
public int Index { get; set; }
9+
public string DiagUrl { get; set; }
10+
public string BaseQueryUrl { get; set; }
11+
public string Region { get; set; }
12+
}
13+
}

src/PackageLagMonitor/Job.cs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
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.Collections.Generic;
6+
using System.ComponentModel.Design;
7+
using System.Linq;
8+
using System.Net.Http;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Autofac;
12+
using Autofac.Extensions.DependencyInjection;
13+
using Microsoft.ApplicationInsights;
14+
using Microsoft.Extensions.Configuration;
15+
using Microsoft.Extensions.DependencyInjection;
16+
using Microsoft.Extensions.Logging;
17+
using Microsoft.Extensions.Options;
18+
using Newtonsoft.Json;
19+
using NuGet.Jobs.Montoring.PackageLag.Telemetry;
20+
using NuGet.Protocol.Catalog;
21+
using NuGet.Services.AzureManagement;
22+
using NuGet.Services.Configuration;
23+
using NuGet.Services.KeyVault;
24+
using NuGet.Services.Logging;
25+
26+
namespace NuGet.Jobs.Montoring.PackageLag
27+
{
28+
public class Job : JobBase
29+
{
30+
private const string ConfigurationArgument = "Configuration";
31+
32+
private const string AzureManagementSectionName = "AzureManagement";
33+
private const string MonitorConfigurationSectionName = "MonitorConfiguration";
34+
35+
/// <summary>
36+
/// To be used for <see cref="IAzureManagementAPIWrapper"/> request
37+
/// </summary>
38+
private const string ProductionSlot = "production";
39+
private const int MAX_CATALOG_RETRY_COUNT = 5;
40+
41+
private static readonly TimeSpan KeyVaultSecretCachingTimeout = TimeSpan.FromDays(1);
42+
43+
private IAzureManagementAPIWrapper _azureManagementApiWrapper;
44+
private IPackageLagTelemetryService _telemetryService;
45+
private HttpClient _httpClient;
46+
private ICatalogClient _catalogClient;
47+
private IServiceProvider _serviceProvider;
48+
private PackageLagMonitorConfiguration _configuration;
49+
50+
public override void Init(IServiceContainer serviceContainer, IDictionary<string, string> jobArgsDictionary)
51+
{
52+
var configurationFilename = JobConfigurationManager.GetArgument(jobArgsDictionary, ConfigurationArgument);
53+
_serviceProvider = GetServiceProvider(GetConfigurationRoot(configurationFilename));
54+
55+
_configuration = _serviceProvider.GetService<PackageLagMonitorConfiguration>();
56+
_azureManagementApiWrapper = _serviceProvider.GetService<AzureManagementAPIWrapper>();
57+
_catalogClient = _serviceProvider.GetService<CatalogClient>();
58+
_httpClient = _serviceProvider.GetService<HttpClient>();
59+
60+
_telemetryService = _serviceProvider.GetService<IPackageLagTelemetryService>();
61+
}
62+
63+
private IConfigurationRoot GetConfigurationRoot(string configurationFilename)
64+
{
65+
Logger.LogInformation("Using the {ConfigurationFilename} configuration file", configurationFilename);
66+
var builder = new ConfigurationBuilder()
67+
.SetBasePath(Environment.CurrentDirectory)
68+
.AddJsonFile(configurationFilename, optional: false, reloadOnChange: true);
69+
70+
var uninjectedConfiguration = builder.Build();
71+
72+
var secretReaderFactory = new ConfigurationRootSecretReaderFactory(uninjectedConfiguration);
73+
var cachingSecretReaderFactory = new CachingSecretReaderFactory(secretReaderFactory, KeyVaultSecretCachingTimeout);
74+
var secretInjector = cachingSecretReaderFactory.CreateSecretInjector(cachingSecretReaderFactory.CreateSecretReader());
75+
76+
builder = new ConfigurationBuilder()
77+
.SetBasePath(Environment.CurrentDirectory)
78+
.AddInjectedJsonFile(configurationFilename, secretInjector);
79+
80+
return builder.Build();
81+
}
82+
83+
private IServiceProvider GetServiceProvider(IConfigurationRoot configurationRoot)
84+
{
85+
var services = new ServiceCollection();
86+
ConfigureLibraries(services);
87+
ConfigureJobServices(services, configurationRoot);
88+
89+
return CreateProvider(services);
90+
}
91+
92+
private void ConfigureLibraries(IServiceCollection services)
93+
{
94+
// we do not call services.AddOptions here, because we want our own custom version of IOptionsSnapshot
95+
// to be present in the service collection for KeyVault secret injection to work properly
96+
services.Add(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(NonCachingOptionsSnapshot<>)));
97+
services.AddSingleton(LoggerFactory);
98+
services.AddLogging();
99+
}
100+
101+
private void ConfigureJobServices(IServiceCollection services, IConfigurationRoot configurationRoot)
102+
{
103+
services.Configure<PackageLagMonitorConfiguration>(configurationRoot.GetSection(MonitorConfigurationSectionName));
104+
services.Configure<AzureManagementAPIWrapperConfiguration>(configurationRoot.GetSection(AzureManagementSectionName));
105+
106+
services.AddSingleton(p =>
107+
{
108+
var handler = new HttpClientHandler();
109+
handler.ServerCertificateCustomValidationCallback =
110+
(httpRequestMessage, cert, cetChain, policyErrors) =>
111+
{
112+
if (policyErrors == System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch || policyErrors == System.Net.Security.SslPolicyErrors.None)
113+
{
114+
return true;
115+
}
116+
117+
return false;
118+
};
119+
return handler;
120+
});
121+
122+
services.AddSingleton(p => new HttpClient(p.GetService<HttpClientHandler>()));
123+
services.AddTransient<IPackageLagTelemetryService, PackageLagTelemetryService>();
124+
services.AddSingleton(new TelemetryClient());
125+
services.AddTransient<ITelemetryClient, TelemetryClientWrapper>();
126+
services.AddTransient<IAzureManagementAPIWrapperConfiguration>(p => p.GetService<IOptionsSnapshot<AzureManagementAPIWrapperConfiguration>>().Value);
127+
services.AddTransient<PackageLagMonitorConfiguration>(p => p.GetService<IOptionsSnapshot<PackageLagMonitorConfiguration>>().Value);
128+
services.AddSingleton<CatalogClient>();
129+
services.AddSingleton<AzureManagementAPIWrapper>();
130+
}
131+
132+
private static IServiceProvider CreateProvider(IServiceCollection services)
133+
{
134+
var containerBuilder = new ContainerBuilder();
135+
containerBuilder.Populate(services);
136+
137+
return new AutofacServiceProvider(containerBuilder.Build());
138+
}
139+
140+
public async override Task Run()
141+
{
142+
var token = new CancellationToken();
143+
try
144+
{
145+
var instances = await GetSearchEndpointsAsync(token);
146+
147+
var maxCommit = DateTimeOffset.MinValue;
148+
149+
foreach (Instance instance in instances)
150+
{
151+
try
152+
{
153+
using (var diagResponse = await _httpClient.GetAsync(
154+
instance.DiagUrl,
155+
HttpCompletionOption.ResponseContentRead,
156+
token))
157+
{
158+
var diagContent = diagResponse.Content;
159+
var searchDiagResultRaw = await diagContent.ReadAsStringAsync();
160+
var searchDiagResultObject = JsonConvert.DeserializeObject<SearchDiagnosticResponse>(searchDiagResultRaw);
161+
162+
var commitDateTime = DateTimeOffset.Parse(searchDiagResultObject.CommitUserData.CommitTimeStamp);
163+
164+
maxCommit = commitDateTime > maxCommit ? commitDateTime : maxCommit;
165+
}
166+
}
167+
catch (Exception e)
168+
{
169+
Logger.LogError("An exception was encountered so no HTTP response was returned. {Exception}", e);
170+
}
171+
}
172+
173+
if (maxCommit == DateTimeOffset.MinValue)
174+
{
175+
Logger.LogError("Failed to retrieve a proper starting commit. Abandoning the current run.");
176+
return;
177+
}
178+
179+
var catalogLeafProcessor = new PackageLagCatalogLeafProcessor(instances, _httpClient, _telemetryService, LoggerFactory.CreateLogger<PackageLagCatalogLeafProcessor>());
180+
181+
var settings = new CatalogProcessorSettings
182+
{
183+
ServiceIndexUrl = _configuration.ServiceIndexUrl,
184+
DefaultMinCommitTimestamp = maxCommit,
185+
ExcludeRedundantLeaves = false
186+
};
187+
188+
var start = new FileCursor("cursor.json", LoggerFactory.CreateLogger<FileCursor>());
189+
await start.SetAsync(maxCommit.AddTicks(1));
190+
191+
var catalogProcessor = new CatalogProcessor(start, _catalogClient, catalogLeafProcessor, settings, LoggerFactory.CreateLogger<CatalogProcessor>());
192+
193+
bool success;
194+
int retryCount = 0;
195+
do
196+
{
197+
success = await catalogProcessor.ProcessAsync();
198+
if (!success || !await catalogLeafProcessor.WaitForProcessing())
199+
{
200+
retryCount++;
201+
Logger.LogError("Processing the catalog leafs failed. Retry Count {CatalogProcessRetryCount}", retryCount);
202+
}
203+
}
204+
while (!success && retryCount < MAX_CATALOG_RETRY_COUNT);
205+
206+
return;
207+
}
208+
catch (Exception e)
209+
{
210+
Logger.LogError("Exception Occured. {Exception}", e);
211+
return;
212+
}
213+
}
214+
215+
private async Task<List<Instance>> GetSearchEndpointsAsync(CancellationToken token)
216+
{
217+
var regionInformations = _configuration.RegionInformations;
218+
var subscription = _configuration.Subscription;
219+
var instances = new List<Instance>();
220+
221+
foreach (var regionInformation in regionInformations)
222+
{
223+
string result = await _azureManagementApiWrapper.GetCloudServicePropertiesAsync(
224+
subscription,
225+
regionInformation.ResourceGroup,
226+
regionInformation.ServiceName,
227+
ProductionSlot,
228+
token);
229+
230+
var cloudService = AzureHelper.ParseCloudServiceProperties(result);
231+
232+
instances.AddRange(GetInstances(cloudService.Uri, cloudService.InstanceCount, regionInformation.Region));
233+
}
234+
235+
return instances;
236+
}
237+
238+
private List<Instance> GetInstances(Uri endpointUri, int instanceCount, string region)
239+
{
240+
var instancePortMinimum = _configuration.InstancePortMinimum;
241+
242+
Logger.LogInformation("Testing {InstanceCount} instances, starting at port {InstancePortMinimum}.", instanceCount, instancePortMinimum);
243+
244+
return Enumerable
245+
.Range(0, instanceCount)
246+
.Select(i =>
247+
{
248+
var diagUriBuilder = new UriBuilder(endpointUri);
249+
250+
diagUriBuilder.Scheme = "https";
251+
diagUriBuilder.Port = instancePortMinimum + i;
252+
diagUriBuilder.Path = "search/diag";
253+
254+
var queryBaseUriBuilder = new UriBuilder(endpointUri);
255+
256+
queryBaseUriBuilder.Scheme = "https";
257+
queryBaseUriBuilder.Port = instancePortMinimum + i;
258+
queryBaseUriBuilder.Path = "search/query";
259+
260+
return new Instance
261+
{
262+
Index = i,
263+
DiagUrl = diagUriBuilder.Uri.ToString(),
264+
BaseQueryUrl = queryBaseUriBuilder.Uri.ToString(),
265+
Region = region
266+
};
267+
})
268+
.ToList();
269+
}
270+
}
271+
}

0 commit comments

Comments
 (0)