Skip to content

Commit 1336379

Browse files
SearchOnCircuitBreaker (#6897)
Add first changes to have Gallery search service use Polly circuit breaker.
1 parent bfd3cae commit 1336379

39 files changed

Lines changed: 1741 additions & 89 deletions
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.Threading.Tasks;
5+
using Newtonsoft.Json.Linq;
6+
using NuGet.Services.Search.Models;
7+
8+
namespace NuGet.Services.Search.Client
9+
{
10+
public interface ISearchClient
11+
{
12+
/// <summary>
13+
/// Performs the Search based on the parameters.
14+
/// </summary>
15+
/// <param name="query">The query string.</param>
16+
/// <param name="projectTypeFilter">ProjectType</param>
17+
/// <param name="includePrerelease">IncludePrerelease</param>
18+
/// <param name="sortBy">SortBy</param>
19+
/// <param name="skip">Skip</param>
20+
/// <param name="take">Take</param>
21+
/// <param name="isLuceneQuery">IsLuceneQuery</param>
22+
/// <param name="countOnly">CountOnly</param>
23+
/// <param name="explain">Explain</param>
24+
/// <param name="getAllVersions">GetAllVersions</param>
25+
/// <param name="supportedFramework">SupportedFramework</param>
26+
/// <param name="semVerLevel">SemVerLevel</param>
27+
/// <returns></returns>
28+
Task<ServiceResponse<SearchResults>> Search(
29+
string query,
30+
string projectTypeFilter,
31+
bool includePrerelease,
32+
SortOrder sortBy,
33+
int skip,
34+
int take,
35+
bool isLuceneQuery,
36+
bool countOnly,
37+
bool explain,
38+
bool getAllVersions,
39+
string supportedFramework,
40+
string semVerLevel);
41+
42+
/// <summary>
43+
/// Returns the search diag.
44+
/// </summary>
45+
/// <returns></returns>
46+
Task<ServiceResponse<JObject>> GetDiagnostics();
47+
}
48+
}

src/NuGet.Services.Search.Client/Client/SearchClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
namespace NuGet.Services.Search.Client
1414
{
15-
public class SearchClient
15+
public class SearchClient : ISearchClient
1616
{
1717
private readonly RetryingHttpClientWrapper _retryingHttpClientWrapper;
1818
private readonly ServiceDiscoveryClient _discoveryClient;

src/NuGet.Services.Search.Client/NuGet.Services.Search.Client.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<Compile Include="Client\BaseUrlHealthIndicatorStore.cs" />
4949
<Compile Include="Client\IEndpointHealthIndicatorStore.cs" />
5050
<Compile Include="Client\IHealthIndicatorLogger.cs" />
51+
<Compile Include="Client\ISearchClient.cs" />
5152
<Compile Include="Client\IServiceDiscoveryClient.cs" />
5253
<Compile Include="Client\NullHealthIndicatorLogger.cs" />
5354
<Compile Include="Client\RetryingHttpClientWrapper.cs" />

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.IO;
99
using System.Linq;
1010
using System.Net;
11+
using System.Net.Http;
1112
using System.Net.Mail;
1213
using System.Security.Principal;
1314
using System.Threading.Tasks;
@@ -16,8 +17,11 @@
1617
using System.Web.Mvc;
1718
using AnglicanGeek.MarkdownMailer;
1819
using Autofac;
20+
using Autofac.Extensions.DependencyInjection;
1921
using Autofac.Core;
2022
using Elmah;
23+
using Microsoft.ApplicationInsights.Extensibility;
24+
using Microsoft.Extensions.DependencyInjection;
2125
using Microsoft.Extensions.Logging;
2226
using Microsoft.WindowsAzure.ServiceRuntime;
2327
using NuGet.Services.Entities;
@@ -28,6 +32,7 @@
2832
using NuGet.Services.Messaging;
2933
using NuGet.Services.Messaging.Email;
3034
using NuGet.Services.Search.Client;
35+
using NuGet.Services.Search.Client.Correlation;
3136
using NuGet.Services.ServiceBus;
3237
using NuGet.Services.Sql;
3338
using NuGet.Services.Validation;
@@ -42,8 +47,8 @@
4247
using NuGetGallery.Features;
4348
using NuGetGallery.Infrastructure;
4449
using NuGetGallery.Infrastructure.Authentication;
45-
using NuGetGallery.Infrastructure.Lucene;
4650
using NuGetGallery.Infrastructure.Mail;
51+
using NuGetGallery.Infrastructure.Search;
4752
using NuGetGallery.Security;
4853
using SecretReaderFactory = NuGetGallery.Configuration.SecretReader.SecretReaderFactory;
4954

@@ -62,15 +67,30 @@ public static class BindingKeys
6267

6368
protected override void Load(ContainerBuilder builder)
6469
{
65-
var loggerConfiguration = LoggingSetup.CreateDefaultLoggerConfiguration(withConsoleLogger: false);
66-
var loggerFactory = LoggingSetup.CreateLoggerFactory(loggerConfiguration);
67-
builder.RegisterInstance(loggerFactory)
70+
var services = new ServiceCollection();
71+
72+
var configuration = new ConfigurationService();
73+
var secretReaderFactory = new SecretReaderFactory(configuration);
74+
var secretReader = secretReaderFactory.CreateSecretReader();
75+
var secretInjector = secretReaderFactory.CreateSecretInjector(secretReader);
76+
77+
builder.RegisterInstance(secretInjector)
6878
.AsSelf()
69-
.As<ILoggerFactory>();
70-
builder.RegisterGeneric(typeof(Logger<>))
71-
.As(typeof(ILogger<>))
79+
.As<ISecretInjector>()
7280
.SingleInstance();
7381

82+
configuration.SecretInjector = secretInjector;
83+
84+
// Register the ILoggerFactory and configure it to use AppInsights if an instrumentation key is provided.
85+
var instrumentationKey = configuration.Current.AppInsightsInstrumentationKey;
86+
if (!string.IsNullOrEmpty(instrumentationKey))
87+
{
88+
TelemetryConfiguration.Active.InstrumentationKey = instrumentationKey;
89+
}
90+
91+
var loggerConfiguration = LoggingSetup.CreateDefaultLoggerConfiguration(withConsoleLogger: false);
92+
var loggerFactory = LoggingSetup.CreateLoggerFactory(loggerConfiguration);
93+
7494
var telemetryClient = TelemetryClientWrapper.Instance;
7595
builder.RegisterInstance(telemetryClient)
7696
.AsSelf()
@@ -83,17 +103,8 @@ protected override void Load(ContainerBuilder builder)
83103
.As<IDiagnosticsService>()
84104
.SingleInstance();
85105

86-
var configuration = new ConfigurationService();
87-
var secretReaderFactory = new SecretReaderFactory(configuration);
88-
var secretReader = secretReaderFactory.CreateSecretReader();
89-
var secretInjector = secretReaderFactory.CreateSecretInjector(secretReader);
90-
91-
builder.RegisterInstance(secretInjector)
92-
.AsSelf()
93-
.As<ISecretInjector>()
94-
.SingleInstance();
95-
96-
configuration.SecretInjector = secretInjector;
106+
services.AddSingleton(loggerFactory);
107+
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
97108

98109
UrlHelperExtensions.SetConfigurationService(configuration);
99110

@@ -113,7 +124,9 @@ protected override void Load(ContainerBuilder builder)
113124
builder.Register(c => configuration.PackageDelete)
114125
.As<IPackageDeleteConfiguration>();
115126

116-
builder.RegisterType<TelemetryService>()
127+
var telemetryService = new TelemetryService(diagnosticsService, telemetryClient);
128+
builder.RegisterInstance(telemetryService)
129+
.AsSelf()
117130
.As<ITelemetryService>()
118131
.As<IFeatureFlagTelemetryService>()
119132
.SingleInstance();
@@ -125,6 +138,7 @@ protected override void Load(ContainerBuilder builder)
125138
.As<Lucene.Net.Store.Directory>()
126139
.SingleInstance();
127140

141+
ConfigureResilientSearch(loggerFactory, configuration, telemetryService, services);
128142
ConfigureSearch(builder, configuration);
129143

130144
builder.RegisterType<DateTimeProvider>().AsSelf().As<IDateTimeProvider>().SingleInstance();
@@ -422,6 +436,7 @@ protected override void Load(ContainerBuilder builder)
422436
}
423437

424438
ConfigureAutocomplete(builder, configuration);
439+
builder.Populate(services);
425440
}
426441

427442
private static void RegisterFeatureFlagsService(ContainerBuilder builder, ConfigurationService configuration)
@@ -694,6 +709,55 @@ private static void ConfigureSearch(ContainerBuilder builder, IGalleryConfigurat
694709
}
695710
}
696711

712+
private static List<(string name,Uri searchUri)> GetSearchClientsFromConfiguration(IGalleryConfigurationService configuration)
713+
{
714+
List<(string name, Uri searchUri)> searchClients = new List<(string name, Uri searchUri)>();
715+
if (configuration.Current.SearchServiceUriPrimary != null)
716+
{
717+
searchClients.Add((SearchClientConfiguration.SearchPrimaryInstance, configuration.Current.SearchServiceUriPrimary));
718+
}
719+
if (configuration.Current.SearchServiceUriSecondary != null)
720+
{
721+
searchClients.Add((SearchClientConfiguration.SearchSecondaryInstance, configuration.Current.SearchServiceUriSecondary));
722+
}
723+
724+
return searchClients;
725+
}
726+
727+
private static void ConfigureResilientSearch(ILoggerFactory loggerFactory, IGalleryConfigurationService configuration, ITelemetryService telemetryService, ServiceCollection services)
728+
{
729+
var searchClients = GetSearchClientsFromConfiguration(configuration);
730+
731+
if (searchClients.Count >= 1)
732+
{
733+
var logger = loggerFactory.CreateLogger<SearchClientPolicies>();
734+
services.AddTransient<CorrelatingHttpClientHandler>();
735+
services.AddTransient((s) => new TracingHttpHandler(DependencyResolver.Current.GetService<IDiagnosticsService>().SafeGetSource("ExternalSearchService")));
736+
737+
foreach(var searchClient in searchClients)
738+
{
739+
// The policy handlers will be applied from the bottom to the top.
740+
// The most inner one is the one added last.
741+
services.AddHttpClient<IHttpClientWrapper, HttpClientWrapper>(searchClient.name, c =>
742+
c.BaseAddress = searchClient.searchUri)
743+
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { AllowAutoRedirect = true })
744+
.AddHttpMessageHandler<TracingHttpHandler>()
745+
.AddHttpMessageHandler<CorrelatingHttpClientHandler>()
746+
.AddPolicyHandler(SearchClientPolicies.SearchClientFallBackCircuitBreakerPolicy(logger, searchClient.name, telemetryService))
747+
.AddPolicyHandler(SearchClientPolicies.SearchClientWaitAndRetryForeverPolicy(logger, searchClient.name, telemetryService))
748+
.AddPolicyHandler(SearchClientPolicies.SearchClientCircuitBreakerPolicy(
749+
SearchClientConfiguration.SearchRetryCount,
750+
TimeSpan.FromSeconds(configuration.Current.SearchCircuitBreakerDelayInSeconds),
751+
logger,
752+
searchClient.name,
753+
telemetryService));
754+
}
755+
services.AddTransient<IResilientSearchClient, ResilientSearchHttpClient>();
756+
services.AddTransient<ISearchClient, GallerySearchClient>();
757+
}
758+
}
759+
760+
697761
private static void ConfigureAutocomplete(ContainerBuilder builder, IGalleryConfigurationService configuration)
698762
{
699763
if (configuration.Current.ServiceDiscoveryUri != null &&

src/NuGetGallery/Areas/Admin/Controllers/LuceneController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using NuGetGallery.Areas.Admin.Models;
88
using NuGetGallery.Configuration;
99
using NuGetGallery.Diagnostics;
10-
using NuGetGallery.Infrastructure.Lucene;
10+
using NuGetGallery.Infrastructure.Search;
1111

1212
namespace NuGetGallery.Areas.Admin.Controllers
1313
{

src/NuGetGallery/Configuration/AppConfiguration.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,5 +367,17 @@ public string ExternalBrandingMessage
367367

368368
[DefaultValue(true)]
369369
public bool AllowLicenselessPackages { get; set; }
370+
371+
/// <summary>
372+
/// The Uri for the Primary Search endpoint
373+
/// </summary>
374+
public Uri SearchServiceUriPrimary { get; set; }
375+
376+
/// <summary>
377+
/// The Uri for the Secondary Search endpoint
378+
/// </summary>
379+
public Uri SearchServiceUriSecondary { get; set; }
380+
381+
public int SearchCircuitBreakerDelayInSeconds { get; set; }
370382
}
371383
}

src/NuGetGallery/Configuration/IAppConfiguration.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,5 +390,20 @@ public interface IAppConfiguration : IMessageServiceConfiguration
390390
/// no embedded license) are allowed into Gallery.
391391
/// </summary>
392392
bool AllowLicenselessPackages { get; set; }
393+
394+
/// <summary>
395+
/// The Uri for the Primary Search endpoint
396+
/// </summary>
397+
Uri SearchServiceUriPrimary { get; set; }
398+
399+
/// <summary>
400+
/// The Uri for the Secondary Search endpoint
401+
/// </summary>
402+
Uri SearchServiceUriSecondary { get; set; }
403+
404+
/// <summary>
405+
/// The time in seconds for the circuit breaker delay. (The time the circuit breaker will stay in open state)
406+
/// </summary>
407+
int SearchCircuitBreakerDelayInSeconds { get; set; }
393408
}
394409
}

src/NuGetGallery/Controllers/ODataV2FeedController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
using NuGet.Services.Entities;
1414
using NuGet.Versioning;
1515
using NuGetGallery.Configuration;
16-
using NuGetGallery.Infrastructure.Lucene;
16+
using NuGetGallery.Infrastructure.Search;
1717
using NuGetGallery.OData;
1818
using NuGetGallery.OData.QueryFilter;
1919
using NuGetGallery.OData.QueryInterceptors;

src/NuGetGallery/Controllers/PackagesController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
using NuGetGallery.Filters;
3030
using NuGetGallery.Helpers;
3131
using NuGetGallery.Infrastructure;
32-
using NuGetGallery.Infrastructure.Lucene;
3332
using NuGetGallery.Infrastructure.Mail.Messages;
3433
using NuGetGallery.Infrastructure.Mail.Requests;
34+
using NuGetGallery.Infrastructure.Search;
3535
using NuGetGallery.OData;
3636
using NuGetGallery.Packaging;
3737
using NuGetGallery.Security;

src/NuGetGallery/Helpers/UriExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ public static string AppendQueryStringToRelativeUri(string relativeUrl, IReadOnl
8484
return builder.Uri.PathAndQuery;
8585
}
8686

87+
public static Uri AppendPathToUri(this Uri uri, string pathToAppend, string queryString = null)
88+
{
89+
var builder = new UriBuilder(uri);
90+
builder.Path = builder.Path.TrimEnd('/') + "/" + pathToAppend.TrimStart('/');
91+
if (!string.IsNullOrEmpty(queryString))
92+
{
93+
builder.Query = queryString;
94+
}
95+
return builder.Uri;
96+
}
97+
8798
public static string GetExternalUrlAnchorTag(string data, string link)
8899
{
89100
return string.Format(ExternalLinkAnchorTagFormat, data, link);

0 commit comments

Comments
 (0)