Skip to content

Commit cf4911e

Browse files
Add the feature flag for the GallerySearch (#6947)
Add the feature flag for the Gallery Search
1 parent 8709b7d commit cf4911e

10 files changed

Lines changed: 63 additions & 22 deletions

src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public class ExternalSearchService : ISearchService, IIndexingService, IRawSearc
2424
private static IEndpointHealthIndicatorStore _healthIndicatorStore;
2525
// Search client that will be deprecated. It is still needed to allow the feature flag until the new search implementation is enabled.
2626
private static ISearchClient _deprecatedSearchClient;
27-
private ISearchClient _searchClient;
27+
private readonly ISearchClient _searchClient;
28+
private readonly IFeatureFlagService _featureFlagService;
2829

2930
private JObject _diagCache;
3031

@@ -65,10 +66,11 @@ public ExternalSearchService()
6566
}
6667
}
6768

68-
public ExternalSearchService(IAppConfiguration config, IDiagnosticsService diagnostics, ISearchClient searchClient)
69+
public ExternalSearchService(IAppConfiguration config, IDiagnosticsService diagnostics, ISearchClient searchClient, IFeatureFlagService featureFlagService)
6970
{
7071
ServiceUri = config.ServiceDiscoveryUri;
7172
_searchClient = searchClient ?? throw new ArgumentNullException(nameof(searchClient));
73+
_featureFlagService = featureFlagService ?? throw new ArgumentNullException(nameof(featureFlagService));
7274

7375
Trace = diagnostics.SafeGetSource("ExternalSearchService");
7476

@@ -330,7 +332,10 @@ internal static Package ReadPackage(JObject doc, string semVerLevel)
330332
/// It will return the client to use based on the feature flag.
331333
/// </summary>
332334
/// <returns>The search client in use. Used for the unit tests.</returns>
333-
internal ISearchClient GetClient(){ return _deprecatedSearchClient; }
335+
internal ISearchClient GetClient()
336+
{
337+
return _featureFlagService.IsSearchCircuitBreakerEnabled() ? _searchClient : _deprecatedSearchClient;
338+
}
334339

335340
// Bunch of no-ops to disable indexing because an external search service is doing that.
336341
public void UpdateIndex()

src/NuGetGallery/Queries/AutocompleteServicePackageIdsQuery.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ namespace NuGetGallery
1212
public class AutocompleteServicePackageIdsQuery
1313
: AutocompleteServiceQuery, IAutocompletePackageIdsQuery
1414
{
15-
public AutocompleteServicePackageIdsQuery(IAppConfiguration configuration, IResilientSearchClient resilientSearchClient)
16-
: base(configuration, resilientSearchClient)
15+
public AutocompleteServicePackageIdsQuery(IAppConfiguration configuration, IResilientSearchClient resilientSearchClient, IFeatureFlagService featureFlagService)
16+
: base(configuration, resilientSearchClient, featureFlagService)
1717
{
1818
}
1919

src/NuGetGallery/Queries/AutocompleteServicePackageVersionsQuery.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ namespace NuGetGallery
1212
public class AutocompleteServicePackageVersionsQuery
1313
: AutocompleteServiceQuery, IAutocompletePackageVersionsQuery
1414
{
15-
public AutocompleteServicePackageVersionsQuery(IAppConfiguration configuration, IResilientSearchClient resilientSearchClient)
16-
: base(configuration, resilientSearchClient)
15+
public AutocompleteServicePackageVersionsQuery(IAppConfiguration configuration, IResilientSearchClient resilientSearchClient, IFeatureFlagService featureFlagService)
16+
: base(configuration, resilientSearchClient, featureFlagService)
1717
{
1818
}
1919

src/NuGetGallery/Queries/AutocompleteServiceQuery.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ public class AutocompleteServiceQuery
2121
private readonly string _autocompleteServiceResourceType;
2222
private readonly RetryingHttpClientWrapper _httpClientToDeprecate;
2323
private readonly IResilientSearchClient _resilientSearchClient;
24+
private readonly IFeatureFlagService _featureFlagService;
2425

25-
public AutocompleteServiceQuery(IAppConfiguration configuration, IResilientSearchClient resilientSearchClient)
26+
public AutocompleteServiceQuery(IAppConfiguration configuration, IResilientSearchClient resilientSearchClient, IFeatureFlagService featureFlagService)
2627
{
2728
if (configuration == null)
2829
{
@@ -33,6 +34,7 @@ public AutocompleteServiceQuery(IAppConfiguration configuration, IResilientSearc
3334
_autocompleteServiceResourceType = configuration.AutocompleteServiceResourceType;
3435
_httpClientToDeprecate = new RetryingHttpClientWrapper(new HttpClient(), QuietLog.LogHandledException);
3536
_resilientSearchClient = resilientSearchClient;
37+
_featureFlagService = featureFlagService ?? throw new ArgumentNullException(nameof(featureFlagService));
3638
}
3739

3840
public async Task<IEnumerable<string>> RunServiceQuery(
@@ -41,8 +43,7 @@ public async Task<IEnumerable<string>> RunServiceQuery(
4143
string semVerLevel = null)
4244
{
4345
queryString = BuildQueryString(queryString, includePrerelease, semVerLevel);
44-
// This will be under the feature flag.
45-
var result = await DeprecatedExecuteQuery(queryString);
46+
var result = _featureFlagService.IsSearchCircuitBreakerEnabled() ? await ExecuteQuery(queryString) : await DeprecatedExecuteQuery(queryString);
4647
var resultObject = JObject.Parse(result);
4748

4849
return resultObject["data"].Select(entry => entry.ToString());

src/NuGetGallery/Services/FeatureFlagService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class FeatureFlagService : IFeatureFlagService
1919
private const string PackagesAtomFeedFeatureName = GalleryPrefix + "PackagesAtomFeed";
2020

2121
private const string ManageDeprecationFeatureName = GalleryPrefix + "ManageDeprecation";
22+
private const string SearchCircuitBreakerFeatureName = GalleryPrefix + "SearchCircuitBreaker";
2223

2324
private readonly IFeatureFlagClient _client;
2425

@@ -51,5 +52,10 @@ private bool IsEnabled(string flight, User user, bool defaultValue)
5152
{
5253
return _client.IsEnabled(flight, user, defaultValue);
5354
}
55+
56+
public bool IsSearchCircuitBreakerEnabled()
57+
{
58+
return _client.IsEnabled(SearchCircuitBreakerFeatureName, defaultValue: false);
59+
}
5460
}
5561
}

src/NuGetGallery/Services/IFeatureFlagService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,11 @@ public interface IFeatureFlagService
3636
/// <param name="user"></param>
3737
/// <returns></returns>
3838
bool IsManageDeprecationEnabled(User user);
39+
40+
/// <summary>
41+
/// Whether or not the search is using circuit breaker.
42+
/// </summary>
43+
/// <returns></returns>
44+
bool IsSearchCircuitBreakerEnabled();
3945
}
4046
}

tests/NuGetGallery.Facts/AutocompleteServicePackageIdsQueryFacts.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,32 @@ private IResilientSearchClient GetResilientSearchClient()
4242
return new ResilientSearchHttpClient(clients, GetLogger(), mockTelemetryService.Object);
4343
}
4444

45+
private IFeatureFlagService GetFeatureFlagService()
46+
{
47+
var mockTelemetryService = new Mock<IFeatureFlagService>();
48+
return mockTelemetryService.Object;
49+
}
50+
4551
[Fact]
4652
public async Task ExecuteReturns30ResultsForEmptyQuery()
4753
{
48-
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient());
54+
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient(), GetFeatureFlagService());
4955
var result = await query.Execute("", false);
5056
Assert.True(result.Count() == 30);
5157
}
5258

5359
[Fact]
5460
public async Task ExecuteReturns30ResultsForNullQuery()
5561
{
56-
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient());
62+
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient(), GetFeatureFlagService());
5763
var result = await query.Execute(null, false);
5864
Assert.True(result.Count() == 30);
5965
}
6066

6167
[Fact]
6268
public async Task ExecuteReturnsResultsForSpecificQuery()
6369
{
64-
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient());
70+
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient(), GetFeatureFlagService());
6571
var result = await query.Execute("jquery", false);
6672
Assert.Contains("jquery", result, StringComparer.OrdinalIgnoreCase);
6773
}
@@ -74,7 +80,7 @@ public async Task ExecuteReturnsResultsForSpecificQuery()
7480
public void PackageIdQueryBuildsCorrectQueryString(bool includePrerelease, string semVerLevel, string expectedQueryString)
7581
{
7682
// Arrange
77-
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient());
83+
var query = new AutocompleteServicePackageIdsQuery(GetConfiguration(), GetResilientSearchClient(), GetFeatureFlagService());
7884

7985
// Act
8086
var actualQueryString = query.BuildQueryString("take=30&q=Json", includePrerelease, semVerLevel);

tests/NuGetGallery.Facts/AutocompleteServicePackageVersionsQueryFacts.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,24 @@ private IResilientSearchClient GetResilientSearchClient()
4242
return new ResilientSearchHttpClient(clients, GetLogger(), mockTelemetryService.Object);
4343
}
4444

45+
private IFeatureFlagService GetFeatureFlagService()
46+
{
47+
var mockTelemetryService = new Mock<IFeatureFlagService>();
48+
return mockTelemetryService.Object;
49+
}
50+
4551
[Fact]
4652
public async Task ExecuteThrowsForEmptyId()
4753
{
48-
var query = new AutocompleteServicePackageVersionsQuery(GetConfiguration(), GetResilientSearchClient());
54+
var query = new AutocompleteServicePackageVersionsQuery(GetConfiguration(), GetResilientSearchClient(), GetFeatureFlagService());
4955
await Assert.ThrowsAsync<ArgumentNullException>(async () => await query.Execute(string.Empty, false));
5056
}
5157

5258
[Fact]
5359
public async Task ExecuteReturnsResultsForSpecificQuery()
5460
{
5561

56-
var query = new AutocompleteServicePackageVersionsQuery(GetConfiguration(), GetResilientSearchClient());
62+
var query = new AutocompleteServicePackageVersionsQuery(GetConfiguration(), GetResilientSearchClient(), GetFeatureFlagService());
5763
var result = await query.Execute("newtonsoft.json", false);
5864
Assert.True(result.Any());
5965
}
@@ -66,7 +72,7 @@ public async Task ExecuteReturnsResultsForSpecificQuery()
6672
public void PackageVersionsQueryBuildsCorrectQueryString(bool includePrerelease, string semVerLevel, string expectedQueryString)
6773
{
6874
// Arrange
69-
var query = new AutocompleteServicePackageVersionsQuery(GetConfiguration(), GetResilientSearchClient());
75+
var query = new AutocompleteServicePackageVersionsQuery(GetConfiguration(), GetResilientSearchClient(), GetFeatureFlagService());
7076

7177
// Act
7278
var actualQueryString = query.BuildQueryString("id=Newtonsoft.Json", includePrerelease, semVerLevel);

tests/NuGetGallery.Facts/AutocompleteServiceQueryFacts.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ private TestAutocompleteServiceQuery(Uri baseAddress, string queryString)
7171
var mockIResilientSearchClient = new Mock<IResilientSearchClient>();
7272
mockIResilientSearchClient.Setup(s => s.GetAsync(_autocompletePath, It.IsAny<string>())).ReturnsAsync(_responseMessage);
7373

74-
_instance = new AutocompleteServiceQuery(mockConfiguration.Object, mockIResilientSearchClient.Object);
74+
var mockTelemetryService = new Mock<IFeatureFlagService>();
75+
76+
_instance = new AutocompleteServiceQuery(mockConfiguration.Object, mockIResilientSearchClient.Object, mockTelemetryService.Object);
7577
}
7678

7779
private static HttpResponseMessage GetResponseMessage(Uri uri, HttpStatusCode statusCode)

tests/NuGetGallery.Facts/Infrastructure/Lucene/ExternalSearchServiceFacts.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ private ILogger<ResilientSearchHttpClient> GetLogger()
4141
return mockConfiguration.Object;
4242
}
4343

44+
private IFeatureFlagService GetFeatureFlagService(bool circuitBreakerIsEnabled)
45+
{
46+
var mockTelemetryService = new Mock<IFeatureFlagService>();
47+
mockTelemetryService.Setup(c => c.IsSearchCircuitBreakerEnabled()).Returns(circuitBreakerIsEnabled);
48+
return mockTelemetryService.Object;
49+
}
50+
4451
private IResilientSearchClient GetResilientSearchClient(string path, string queryString)
4552
{
4653
var content = new JObject(
@@ -65,17 +72,19 @@ private ISearchClient GetSearchClient(string path, string queryString)
6572
return new GallerySearchClient(GetResilientSearchClient(path, queryString));
6673
}
6774

68-
[Fact]
69-
public void ReturnsTheExpectedClient()
75+
[Theory]
76+
[InlineData(true, "NuGetGallery.Infrastructure.Search.GallerySearchClient")]
77+
[InlineData(false, "NuGet.Services.Search.Client.SearchClient")]
78+
public void ReturnsTheExpectedClient(bool circuitBreakerIsEnabled, string expected)
7079
{
7180
// Arrange
72-
var service = new ExternalSearchService(GetConfiguration(), GetDiagnosticsService(), GetSearchClient(string.Empty, string.Empty));
81+
var service = new ExternalSearchService(GetConfiguration(), GetDiagnosticsService(), GetSearchClient(string.Empty, string.Empty), GetFeatureFlagService(circuitBreakerIsEnabled));
7382

7483
// Act
7584
var client = service.GetClient();
7685
var clientType = client.GetType();
7786

78-
Assert.Equal("NuGet.Services.Search.Client.SearchClient", clientType.FullName);
87+
Assert.Equal(expected, clientType.FullName);
7988
}
8089
}
8190

0 commit comments

Comments
 (0)