Skip to content

Commit

Permalink
Modifing structure
Browse files Browse the repository at this point in the history
  • Loading branch information
wAsnk committed Sep 5, 2024
1 parent 80cb103 commit 6b1c8d7
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 25 deletions.
3 changes: 2 additions & 1 deletion Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public static class FeatureNames
public const string AddSiteOwnerPermissionToRole = Maintenance + "." + nameof(AddSiteOwnerPermissionToRole);
public const string RemoveUsers = Maintenance + "." + nameof(RemoveUsers);
public const string ChangeUserSensitiveContent = Maintenance + "." + nameof(ChangeUserSensitiveContent);
public const string DeleteElasticsearchIndices = Maintenance + "." + nameof(DeleteElasticsearchIndices);
public const string DeleteOrRebuildElasticsearchIndices = Maintenance + "." + nameof(DeleteOrRebuildElasticsearchIndices);
public const string DeleteElasticsearchIndicesBeforeSetup = Maintenance + "." + nameof(DeleteElasticsearchIndicesBeforeSetup);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.DeleteElasticsearchIndi

public class DeleteElasticsearchIndicesMaintenanceProvider : MaintenanceProviderBase
{
private readonly IOptions<DeleteElasticsearchIndicesMaintenanceOptions> _options;
private readonly IOptions<ElasticsearchIndicesMaintenanceOptions> _options;
private readonly ElasticIndexManager _elasticIndexManager;

public DeleteElasticsearchIndicesMaintenanceProvider(
IOptions<DeleteElasticsearchIndicesMaintenanceOptions> options,
IOptions<ElasticsearchIndicesMaintenanceOptions> options,
ElasticIndexManager elasticIndexManager)
{
_options = options;
Expand All @@ -22,7 +22,7 @@ public DeleteElasticsearchIndicesMaintenanceProvider(

public override Task<bool> ShouldExecuteAsync(MaintenanceTaskExecutionContext context) =>
Task.FromResult(
_options.Value.MaintenanceIsEnabled &&
_options.Value.DeleteMaintenanceIsEnabled &&
!context.WasLatestExecutionSuccessful());

public override Task ExecuteAsync(MaintenanceTaskExecutionContext context) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
using Elasticsearch.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Nest;
using OrchardCore.Environment.Shell;
using OrchardCore.Locking.Distributed;
using OrchardCore.Search.Elasticsearch.Core.Models;
using OrchardCore.Search.Elasticsearch.Core.Services;
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.DeleteElasticsearchIndices;
Expand Down Expand Up @@ -32,6 +37,12 @@ public DeleteElasticsearchIndicesMiddleware(

public async Task InvokeAsync(HttpContext httpContext)
{
if (httpContext.Request.Method != WebRequestMethods.Http.Get)
{
await _next.Invoke(httpContext);
return;
}

if (await InvokeNextIfUninitializedAsync(_shellSettings, httpContext)) return;

// Try to acquire a lock before starting installation
Expand Down Expand Up @@ -73,4 +84,77 @@ private async Task<bool> InvokeNextIfUninitializedAsync(ShellSettings shellSetti
await _next.Invoke(httpContext);
return true;
}

private static ConnectionSettings GetConnectionSettings(ElasticConnectionOptions elasticConfiguration)
{
// This is a copy of the OC Elasticsearch module's OrchardCore.Search.Elasticsearch.Startup.GetConnectionSettings method.
#pragma warning disable CA2000 // Call System. IDisposable. Dispose on object created by
// 'GetConnectionPool(elasticConfiguration)' before all references to it are out of scope
var pool = GetConnectionPool(elasticConfiguration);
#pragma warning restore CA2000

var settings = new ConnectionSettings(pool);

if (elasticConfiguration.ConnectionType != "CloudConnectionPool" &&
!string.IsNullOrWhiteSpace(elasticConfiguration.Username) &&
!string.IsNullOrWhiteSpace(elasticConfiguration.Password))
{
settings.BasicAuthentication(elasticConfiguration.Username, elasticConfiguration.Password);
}

if (!string.IsNullOrWhiteSpace(elasticConfiguration.CertificateFingerprint))
{
settings.CertificateFingerprint(elasticConfiguration.CertificateFingerprint);
}

if (elasticConfiguration.EnableApiVersioningHeader)
{
settings.EnableApiVersioningHeader();
}

return settings;
}

private static IConnectionPool GetConnectionPool(ElasticConnectionOptions elasticConfiguration)
{
var uris = elasticConfiguration.Ports.Select(port => new Uri($"{elasticConfiguration.Url}:{port.ToTechnicalString()}")).Distinct();
IConnectionPool pool = null;
switch (elasticConfiguration.ConnectionType)
{
case "SingleNodeConnectionPool":
pool = new SingleNodeConnectionPool(uris.First());
break;

case "CloudConnectionPool":
if (!string.IsNullOrWhiteSpace(elasticConfiguration.Username) &&
!string.IsNullOrWhiteSpace(elasticConfiguration.Password) &&
!string.IsNullOrWhiteSpace(elasticConfiguration.CloudId))
{
using var credentials = new BasicAuthenticationCredentials(
elasticConfiguration.Username,
elasticConfiguration.Password);
pool = new CloudConnectionPool(elasticConfiguration.CloudId, credentials);
}

break;

case "StaticConnectionPool":
pool = new StaticConnectionPool(uris);
break;

case "SniffingConnectionPool":
pool = new SniffingConnectionPool(uris);
break;

case "StickyConnectionPool":
pool = new StickyConnectionPool(uris);
break;

default:
pool = new SingleNodeConnectionPool(uris.First());
break;
}

return pool;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.DeleteElasticsearchIndices;

public class ElasticsearchIndicesMaintenanceOptions
{
public bool DeleteMaintenanceIsEnabled { get; set; }
public bool RebuildMaintenanceIsEnabled { get; set; }
public bool BeforeSetupMiddlewareIsEnabled { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Lombiq.Hosting.Tenants.Maintenance.Extensions;
using Lombiq.Hosting.Tenants.Maintenance.Models;
using Lombiq.Hosting.Tenants.Maintenance.Services;
using Microsoft.Extensions.Options;
using OrchardCore.Search.Elasticsearch.Core.Services;
using System.Threading.Tasks;

namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.DeleteElasticsearchIndices;

public class RebuildElasticsearchIndicesMaintenanceProvider : MaintenanceProviderBase
{
private readonly IOptions<ElasticsearchIndicesMaintenanceOptions> _options;
private readonly ElasticIndexingService _elasticIndexingService;
private readonly ElasticIndexSettingsService _elasticIndexSettingsService;

public RebuildElasticsearchIndicesMaintenanceProvider(
IOptions<ElasticsearchIndicesMaintenanceOptions> options,
ElasticIndexingService elasticIndexingService,
ElasticIndexSettingsService elasticIndexSettingsService)
{
_options = options;
_elasticIndexingService = elasticIndexingService;
_elasticIndexSettingsService = elasticIndexSettingsService;
}

public override Task<bool> ShouldExecuteAsync(MaintenanceTaskExecutionContext context) =>
Task.FromResult(
_options.Value.RebuildMaintenanceIsEnabled &&
!context.WasLatestExecutionSuccessful());

public override async Task ExecuteAsync(MaintenanceTaskExecutionContext context)
{
var settings = await _elasticIndexSettingsService.GetSettingsAsync();
foreach (var setting in settings)
{
await _elasticIndexingService.RebuildIndexAsync(setting);

if (setting.QueryAnalyzerName != setting.AnalyzerName)
{
// Query Analyzer may be different until the index in rebuilt.
// Since the index is rebuilt, lets make sure we query using the same analyzer.
setting.QueryAnalyzerName = setting.AnalyzerName;

await _elasticIndexSettingsService.UpdateIndexAsync(setting);
}

await _elasticIndexingService.ProcessContentItemsAsync(setting.IndexName);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,162 @@
using Elasticsearch.Net;
using Lombiq.HelpfulLibraries.OrchardCore.Mvc;
using Lombiq.Hosting.Tenants.Maintenance.Constants;
using Lombiq.Hosting.Tenants.Maintenance.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Nest;
using OrchardCore.Environment.Shell;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Modules;
using OrchardCore.Search.Elasticsearch.Core.Models;
using OrchardCore.Search.Elasticsearch.Core.Services;
using System;
using System.Linq;

namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.DeleteElasticsearchIndices;

[Feature(FeatureNames.DeleteElasticsearchIndices)]
public class Startup : StartupBase
[Feature(FeatureNames.DeleteOrRebuildElasticsearchIndices)]
public class DeleteElasticsearchIndicesStartup : StartupBase
{
private readonly IShellConfiguration _shellConfiguration;

public override int Order => int.MaxValue;

public DeleteElasticsearchIndicesStartup(IShellConfiguration shellConfiguration) => _shellConfiguration = shellConfiguration;

public override void ConfigureServices(IServiceCollection services)
{
services.BindAndConfigureSection<ElasticsearchIndicesMaintenanceOptions>(
_shellConfiguration,
"Lombiq_Hosting_Tenants_Maintenance:ElasticsearchIndicesOptions");

services.AddScoped<IMaintenanceProvider, DeleteElasticsearchIndicesMaintenanceProvider>();
services.AddScoped<IMaintenanceProvider, RebuildElasticsearchIndicesMaintenanceProvider>();
}
}

[Feature(FeatureNames.DeleteElasticsearchIndicesBeforeSetup)]
public class DeleteElasticsearchIndicesBeforeSetupStartup : StartupBase
{
private readonly IShellConfiguration _shellConfiguration;
private readonly ShellSettings _shellSettings;

public Startup(IShellConfiguration shellConfiguration, ShellSettings shellSettings)
public override int Order => int.MaxValue;

public DeleteElasticsearchIndicesBeforeSetupStartup(IShellConfiguration shellConfiguration, ShellSettings shellSettings)
{
_shellConfiguration = shellConfiguration;
_shellSettings = shellSettings;
}

public override void ConfigureServices(IServiceCollection services)
{
services.BindAndConfigureSection<DeleteElasticsearchIndicesMaintenanceOptions>(
services.BindAndConfigureSection<ElasticsearchIndicesMaintenanceOptions>(
_shellConfiguration,
"Lombiq_Hosting_Tenants_Maintenance:DeleteElasticsearchIndices");
"Lombiq_Hosting_Tenants_Maintenance:ElasticsearchIndicesOptions");

services.AddScoped<IMaintenanceProvider, DeleteElasticsearchIndicesMaintenanceProvider>();
// After the setup, the Elasticsearch module can be loaded the regular way.
if (!_shellSettings.IsUninitialized()) return;

// This is necessary because the Elasticsearch module can't be enabled the regular way if this module is added
// as a setup feature, otherwise you get a ContentsAdminList shape missing exception on the admin dashboard. For
// more info see:
var configuration = _shellConfiguration.GetSection("OrchardCore_Elasticsearch");
var elasticConfiguration = configuration.Get<ElasticConnectionOptions>();
services.Configure<ElasticConnectionOptions>(o => o.ConfigurationExists = true);
// Otherwise the ElasticClient won't work. Copied all this from the OC Elasticsearch module.
#pragma warning disable CA2000 // Call System. IDisposable. Dispose on object created by
// 'GetConnectionSettings(elasticConfiguration)' before all references to it are out of scope
var settings = GetConnectionSettings(elasticConfiguration);
#pragma warning restore CA2000
services.AddSingleton<IElasticClient>(new ElasticClient(settings));
services.AddSingleton<ElasticIndexManager>();
}

public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
if (!_shellSettings.IsUninitialized()) return;

var options = serviceProvider.GetRequiredService<IOptions<DeleteElasticsearchIndicesMaintenanceOptions>>().Value;
if (options.MiddlewareIsEnabled)
var options = serviceProvider.GetRequiredService<IOptions<ElasticsearchIndicesMaintenanceOptions>>().Value;
if (options.BeforeSetupMiddlewareIsEnabled)
{
app.UseMiddleware<DeleteElasticsearchIndicesMiddleware>();
}
}

private static ConnectionSettings GetConnectionSettings(ElasticConnectionOptions elasticConfiguration)
{
// This is a copy of the OC Elasticsearch module's OrchardCore.Search.Elasticsearch.Startup.GetConnectionSettings method.
#pragma warning disable CA2000 // Call System. IDisposable. Dispose on object created by
// 'GetConnectionPool(elasticConfiguration)' before all references to it are out of scope
var pool = GetConnectionPool(elasticConfiguration);
#pragma warning restore CA2000

var settings = new ConnectionSettings(pool);

if (elasticConfiguration.ConnectionType != "CloudConnectionPool" &&
!string.IsNullOrWhiteSpace(elasticConfiguration.Username) &&
!string.IsNullOrWhiteSpace(elasticConfiguration.Password))
{
settings.BasicAuthentication(elasticConfiguration.Username, elasticConfiguration.Password);
}

if (!string.IsNullOrWhiteSpace(elasticConfiguration.CertificateFingerprint))
{
settings.CertificateFingerprint(elasticConfiguration.CertificateFingerprint);
}

if (elasticConfiguration.EnableApiVersioningHeader)
{
settings.EnableApiVersioningHeader();
}

return settings;
}

private static IConnectionPool GetConnectionPool(ElasticConnectionOptions elasticConfiguration)
{
var uris = elasticConfiguration.Ports.Select(port => new Uri($"{elasticConfiguration.Url}:{port.ToTechnicalString()}")).Distinct();
IConnectionPool pool = null;
switch (elasticConfiguration.ConnectionType)
{
case "SingleNodeConnectionPool":
pool = new SingleNodeConnectionPool(uris.First());
break;

case "CloudConnectionPool":
if (!string.IsNullOrWhiteSpace(elasticConfiguration.Username) &&
!string.IsNullOrWhiteSpace(elasticConfiguration.Password) &&
!string.IsNullOrWhiteSpace(elasticConfiguration.CloudId))
{
using var credentials = new BasicAuthenticationCredentials(
elasticConfiguration.Username,
elasticConfiguration.Password);
pool = new CloudConnectionPool(elasticConfiguration.CloudId, credentials);
}

break;

case "StaticConnectionPool":
pool = new StaticConnectionPool(uris);
break;

case "SniffingConnectionPool":
pool = new SniffingConnectionPool(uris);
break;

case "StickyConnectionPool":
pool = new StickyConnectionPool(uris);
break;

default:
pool = new SingleNodeConnectionPool(uris.First());
break;
}

return pool;
}
}
13 changes: 10 additions & 3 deletions Lombiq.Hosting.Tenants.Maintenance/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,17 @@
)]

[assembly: Feature(
Id = DeleteElasticsearchIndices,
Id = DeleteOrRebuildElasticsearchIndices,
Name = "Lombiq Hosting - Tenants Maintenance Delete Elasticsearch Indexes",
Description = "Deletes Elasticsearch indexes.",
Category = "Maintenance",
DefaultTenantOnly = true,
Dependencies = [Maintenance, "OrchardCore.Search.Elasticsearch"]
Dependencies = [Maintenance]
)]

[assembly: Feature(
Id = DeleteElasticsearchIndicesBeforeSetup,
Name = "Lombiq Hosting - Tenants Maintenance Delete Elasticsearch Indexes Before Setup",
Description = "Deletes Elasticsearch indexes before setup.",
Category = "Maintenance",
Dependencies = []
)]
Loading

0 comments on commit 6b1c8d7

Please sign in to comment.