Skip to content

Commit ebda79a

Browse files
committed
Add capability of a preview ISearchService (#7171)
Progress on #7152
1 parent 91f884c commit ebda79a

6 files changed

Lines changed: 167 additions & 52 deletions

File tree

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 136 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
using NuGetGallery.Infrastructure.Search.Correlation;
5151
using NuGetGallery.Security;
5252
using SecretReaderFactory = NuGetGallery.Configuration.SecretReader.SecretReaderFactory;
53+
using Microsoft.Extensions.Http;
5354

5455
namespace NuGetGallery
5556
{
@@ -62,6 +63,8 @@ public static class BindingKeys
6263
public const string PackageValidationEnqueuer = "PackageValidationEnqueuerBindingKey";
6364
public const string SymbolsPackageValidationEnqueuer = "SymbolsPackageValidationEnqueuerBindingKey";
6465
public const string EmailPublisherTopic = "EmailPublisherBindingKey";
66+
67+
public const string PreviewSearchClient = "PreviewSearchClientBindingKey";
6568
}
6669

6770
protected override void Load(ContainerBuilder builder)
@@ -137,8 +140,7 @@ protected override void Load(ContainerBuilder builder)
137140
.As<Lucene.Net.Store.Directory>()
138141
.SingleInstance();
139142

140-
ConfigureResilientSearch(loggerFactory, configuration, telemetryService, services);
141-
ConfigureSearch(builder, configuration);
143+
ConfigureSearch(loggerFactory, configuration, telemetryService, services, builder);
142144

143145
builder.RegisterType<DateTimeProvider>().AsSelf().As<IDateTimeProvider>().SingleInstance();
144146

@@ -671,60 +673,104 @@ private void RegisterAsynchronousValidation(ContainerBuilder builder, IDiagnosti
671673
.InstancePerLifetimeScope();
672674
}
673675

674-
private static void ConfigureSearch(ContainerBuilder builder, IGalleryConfigurationService configuration)
676+
private static List<(string name, Uri searchUri)> GetSearchClientsFromConfiguration(IGalleryConfigurationService configuration)
675677
{
676-
if (configuration.Current.SearchServiceUriPrimary == null && configuration.Current.SearchServiceUriSecondary == null)
678+
var searchClients = new List<(string name, Uri searchUri)>();
679+
680+
if (configuration.Current.SearchServiceUriPrimary != null)
677681
{
678-
builder.RegisterType<LuceneSearchService>()
679-
.AsSelf()
680-
.As<ISearchService>()
681-
.InstancePerLifetimeScope();
682-
builder.RegisterType<LuceneIndexingService>()
683-
.AsSelf()
684-
.As<IIndexingService>()
685-
.InstancePerLifetimeScope();
682+
searchClients.Add((SearchClientConfiguration.SearchPrimaryInstance, configuration.Current.SearchServiceUriPrimary));
686683
}
687-
else
684+
if (configuration.Current.SearchServiceUriSecondary != null)
688685
{
689-
builder.RegisterType<ExternalSearchService>()
690-
.AsSelf()
691-
.As<ISearchService>()
692-
.As<IIndexingService>()
693-
.InstancePerLifetimeScope();
686+
searchClients.Add((SearchClientConfiguration.SearchSecondaryInstance, configuration.Current.SearchServiceUriSecondary));
694687
}
688+
689+
return searchClients;
695690
}
696691

697-
private static List<(string name, Uri searchUri)> GetSearchClientsFromConfiguration(IGalleryConfigurationService configuration)
692+
private static List<(string name, Uri searchUri)> GetPreviewSearchClientsFromConfiguration(IGalleryConfigurationService configuration)
698693
{
699-
List<(string name, Uri searchUri)> searchClients = new List<(string name, Uri searchUri)>();
700-
if (configuration.Current.SearchServiceUriPrimary != null)
694+
var searchClients = new List<(string name, Uri searchUri)>();
695+
696+
if (configuration.Current.PreviewSearchServiceUriPrimary != null)
701697
{
702-
searchClients.Add((SearchClientConfiguration.SearchPrimaryInstance, configuration.Current.SearchServiceUriPrimary));
698+
searchClients.Add((SearchClientConfiguration.PreviewSearchPrimaryInstance, configuration.Current.PreviewSearchServiceUriPrimary));
703699
}
704-
if (configuration.Current.SearchServiceUriSecondary != null)
700+
if (configuration.Current.PreviewSearchServiceUriSecondary != null)
705701
{
706-
searchClients.Add((SearchClientConfiguration.SearchSecondaryInstance, configuration.Current.SearchServiceUriSecondary));
702+
searchClients.Add((SearchClientConfiguration.PreviewSearchSecondaryInstance, configuration.Current.PreviewSearchServiceUriSecondary));
707703
}
708704

709705
return searchClients;
710706
}
711707

712-
private static void ConfigureResilientSearch(ILoggerFactory loggerFactory, IGalleryConfigurationService configuration, ITelemetryService telemetryService, ServiceCollection services)
708+
private static void ConfigureSearch(
709+
ILoggerFactory loggerFactory,
710+
IGalleryConfigurationService configuration,
711+
ITelemetryService telemetryService,
712+
ServiceCollection services,
713+
ContainerBuilder builder)
713714
{
714715
var searchClients = GetSearchClientsFromConfiguration(configuration);
715716

716717
if (searchClients.Count >= 1)
717718
{
718-
var logger = loggerFactory.CreateLogger<SearchClientPolicies>();
719719
services.AddTransient<CorrelatingHttpClientHandler>();
720720
services.AddTransient((s) => new TracingHttpHandler(DependencyResolver.Current.GetService<IDiagnosticsService>().SafeGetSource("ExternalSearchService")));
721721

722-
foreach (var searchClient in searchClients)
723-
{
724-
// The policy handlers will be applied from the bottom to the top.
725-
// The most inner one is the one added last.
726-
services.AddHttpClient<IHttpClientWrapper, HttpClientWrapper>(searchClient.name,
727-
c =>
722+
// Register the default search service implementation and its dependencies.
723+
RegisterSearchService(
724+
loggerFactory,
725+
configuration,
726+
telemetryService,
727+
services,
728+
builder,
729+
searchClients);
730+
731+
// Register the preview search service and its dependencies with a binding key.
732+
var previewSearchClients = GetPreviewSearchClientsFromConfiguration(configuration);
733+
RegisterSearchService(
734+
loggerFactory,
735+
configuration,
736+
telemetryService,
737+
services,
738+
builder,
739+
previewSearchClients,
740+
BindingKeys.PreviewSearchClient);
741+
}
742+
else
743+
{
744+
builder.RegisterType<LuceneSearchService>()
745+
.AsSelf()
746+
.As<ISearchService>()
747+
.InstancePerLifetimeScope();
748+
builder.RegisterType<LuceneIndexingService>()
749+
.AsSelf()
750+
.As<IIndexingService>()
751+
.InstancePerLifetimeScope();
752+
}
753+
}
754+
755+
private static void RegisterSearchService(
756+
ILoggerFactory loggerFactory,
757+
IGalleryConfigurationService configuration,
758+
ITelemetryService telemetryService,
759+
ServiceCollection services,
760+
ContainerBuilder builder,
761+
List<(string name, Uri searchUri)> searchClients,
762+
string bindingKey = null)
763+
{
764+
var logger = loggerFactory.CreateLogger<SearchClientPolicies>();
765+
766+
foreach (var searchClient in searchClients)
767+
{
768+
// The policy handlers will be applied from the bottom to the top.
769+
// The most inner one is the one added last.
770+
services
771+
.AddHttpClient<IHttpClientWrapper, HttpClientWrapper>(
772+
searchClient.name,
773+
c =>
728774
{
729775
c.BaseAddress = searchClient.searchUri;
730776
c.Timeout = TimeSpan.FromMilliseconds(configuration.Current.SearchHttpClientTimeoutInMilliseconds);
@@ -744,12 +790,66 @@ private static void ConfigureResilientSearch(ILoggerFactory loggerFactory, IGall
744790
logger,
745791
searchClient.name,
746792
telemetryService));
747-
}
748-
services.AddTransient<IResilientSearchClient, ResilientSearchHttpClient>();
749-
services.AddTransient<ISearchClient, GallerySearchClient>();
750793
}
751-
}
752794

795+
var registrationBuilder = builder
796+
.Register(c =>
797+
{
798+
var httpClientFactory = c.Resolve<IHttpClientFactory>();
799+
var httpClientWrapperFactory = c.Resolve<ITypedHttpClientFactory<HttpClientWrapper>>();
800+
var httpClientWrappers = new List<IHttpClientWrapper>(searchClients.Count);
801+
foreach (var searchClient in searchClients)
802+
{
803+
var httpClient = httpClientFactory.CreateClient(searchClient.name);
804+
var httpClientWrapper = httpClientWrapperFactory.CreateClient(httpClient);
805+
httpClientWrappers.Add(httpClientWrapper);
806+
}
807+
808+
return new ResilientSearchHttpClient(
809+
httpClientWrappers,
810+
c.Resolve<ILogger<ResilientSearchHttpClient>>(),
811+
c.Resolve<ITelemetryService>());
812+
});
813+
814+
if (bindingKey != null)
815+
{
816+
registrationBuilder
817+
.Named<IResilientSearchClient>(bindingKey)
818+
.InstancePerLifetimeScope();
819+
820+
builder
821+
.RegisterType<GallerySearchClient>()
822+
.WithParameter(new ResolvedParameter(
823+
(pi, ctx) => pi.ParameterType == typeof(IResilientSearchClient),
824+
(pi, ctx) => ctx.ResolveKeyed<IResilientSearchClient>(bindingKey)))
825+
.Named<ISearchClient>(bindingKey)
826+
.InstancePerLifetimeScope();
827+
828+
builder.RegisterType<ExternalSearchService>()
829+
.WithParameter(new ResolvedParameter(
830+
(pi, ctx) => pi.ParameterType == typeof(ISearchClient),
831+
(pi, ctx) => ctx.ResolveKeyed<ISearchClient>(bindingKey)))
832+
.Named<ISearchService>(bindingKey)
833+
.InstancePerLifetimeScope();
834+
}
835+
else
836+
{
837+
registrationBuilder
838+
.As<IResilientSearchClient>()
839+
.InstancePerLifetimeScope();
840+
841+
builder
842+
.RegisterType<GallerySearchClient>()
843+
.As<ISearchClient>()
844+
.InstancePerLifetimeScope();
845+
846+
builder.RegisterType<ExternalSearchService>()
847+
.AsSelf()
848+
.As<ISearchService>()
849+
.As<IIndexingService>()
850+
.InstancePerLifetimeScope();
851+
}
852+
}
753853

754854
private static void ConfigureAutocomplete(ContainerBuilder builder, IGalleryConfigurationService configuration)
755855
{

src/NuGetGallery/Configuration/AppConfiguration.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,16 +355,14 @@ public string ExternalBrandingMessage
355355
[DefaultValue(true)]
356356
public bool AllowLicenselessPackages { get; set; }
357357

358-
/// <summary>
359-
/// The Uri for the Primary Search endpoint
360-
/// </summary>
361358
public Uri SearchServiceUriPrimary { get; set; }
362359

363-
/// <summary>
364-
/// The Uri for the Secondary Search endpoint
365-
/// </summary>
366360
public Uri SearchServiceUriSecondary { get; set; }
367361

362+
public Uri PreviewSearchServiceUriPrimary { get; set; }
363+
364+
public Uri PreviewSearchServiceUriSecondary { get; set; }
365+
368366
[DefaultValue(600)]
369367
public int SearchCircuitBreakerDelayInSeconds { get; set; }
370368

src/NuGetGallery/Configuration/IAppConfiguration.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,15 +382,25 @@ public interface IAppConfiguration : IMessageServiceConfiguration
382382
bool AllowLicenselessPackages { get; set; }
383383

384384
/// <summary>
385-
/// The Uri for the Primary Search endpoint
385+
/// The URL for the primary search endpoint, for stable behavior.
386386
/// </summary>
387387
Uri SearchServiceUriPrimary { get; set; }
388388

389389
/// <summary>
390-
/// The Uri for the Secondary Search endpoint
390+
/// The URL for the secondary search endpoint, for stable behavior.
391391
/// </summary>
392392
Uri SearchServiceUriSecondary { get; set; }
393393

394+
/// <summary>
395+
/// The URL for the primary search endpoint, for preview behavior.
396+
/// </summary>
397+
Uri PreviewSearchServiceUriPrimary { get; set; }
398+
399+
/// <summary>
400+
/// The URL for the secondary search endpoint, for preview behavior.
401+
/// </summary>
402+
Uri PreviewSearchServiceUriSecondary { get; set; }
403+
394404
/// <summary>
395405
/// The time in seconds for the circuit breaker delay. (The time the circuit breaker will stay in open state)
396406
/// </summary>

src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.Linq;
8-
using System.Net;
98
using System.Threading.Tasks;
109
using System.Web;
1110
using Newtonsoft.Json.Linq;
@@ -36,10 +35,6 @@ public bool IsLocal
3635

3736
public bool ContainsAllVersions { get { return true; } }
3837

39-
public ExternalSearchService()
40-
{
41-
}
42-
4338
public ExternalSearchService(IAppConfiguration config, IDiagnosticsService diagnostics, ISearchClient searchClient)
4439
{
4540
_searchClient = searchClient ?? throw new ArgumentNullException(nameof(searchClient));

src/NuGetGallery/Infrastructure/Lucene/SearchClientConfiguration.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33

44
namespace NuGetGallery.Infrastructure.Search
55
{
6-
public class SearchClientConfiguration
6+
public static class SearchClientConfiguration
77
{
88
public static string SearchPrimaryInstance = "SearchPrimary";
99
public static string SearchSecondaryInstance = "SearchSecondary";
10+
11+
public static string PreviewSearchPrimaryInstance = "PreviewSearchPrimary";
12+
public static string PreviewSearchSecondaryInstance = "PreviewSearchSecondary";
1013
}
1114
}

tests/NuGetGallery.Facts/Services/FeedServiceFacts.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Moq;
1616
using NuGet.Services.Entities;
1717
using NuGetGallery.Configuration;
18+
using NuGetGallery.Diagnostics;
1819
using NuGetGallery.Infrastructure.Search;
1920
using NuGetGallery.OData;
2021
using NuGetGallery.OData.QueryFilter;
@@ -554,7 +555,11 @@ public async Task V2FeedPackagesUsesSearchHijackForIdOrIdVersionQueries(string f
554555
configuration.Setup(c => c.Features).Returns(new FeatureConfiguration() { FriendlyLicenses = true });
555556
configuration.Setup(c => c.Current).Returns(new AppConfiguration() { IsODataFilterEnabled = false });
556557

557-
var searchService = new Mock<ExternalSearchService>(MockBehavior.Loose);
558+
var searchService = new Mock<ExternalSearchService>(
559+
MockBehavior.Loose,
560+
Mock.Of<IAppConfiguration>(),
561+
Mock.Of<IDiagnosticsService>(),
562+
Mock.Of<ISearchClient>());
558563
searchService.CallBase = true;
559564
searchService
560565
.Setup(x => x.RawSearch(It.IsAny<SearchFilter>()))
@@ -614,7 +619,11 @@ public async Task V2FeedPackagesDoesNotUseSearchHijackForFunkyQueries(string fil
614619
configuration.Setup(c => c.Current).Returns(new AppConfiguration() { IsODataFilterEnabled = false });
615620

616621
bool called = false;
617-
var searchService = new Mock<ExternalSearchService>(MockBehavior.Loose);
622+
var searchService = new Mock<ExternalSearchService>(
623+
MockBehavior.Loose,
624+
Mock.Of<IAppConfiguration>(),
625+
Mock.Of<IDiagnosticsService>(),
626+
Mock.Of<ISearchClient>());
618627
searchService
619628
.Setup(x => x.RawSearch(It.IsAny<SearchFilter>()))
620629
.ReturnsAsync(new SearchResults(0, indexTimestampUtc: null));

0 commit comments

Comments
 (0)