130 lines
5.5 KiB
C#
130 lines
5.5 KiB
C#
using HanaToolbox.Config;
|
|
using HanaToolbox.Logging;
|
|
using HanaToolbox.Services.Interfaces;
|
|
|
|
namespace HanaToolbox.Services;
|
|
|
|
/// <summary>
|
|
/// Performs the Aurora schema refresh:
|
|
/// Drop old _AURORA schema → Export source → Import-rename → Update company name → Grant privileges.
|
|
/// </summary>
|
|
public sealed class AuroraService(
|
|
IUserSwitcher switcher,
|
|
IHdbClientLocator locator,
|
|
INotificationService ntfy,
|
|
AppLogger logger) : IAuroraService
|
|
{
|
|
public async Task RunAsync(
|
|
AuroraConfig config, HanaConfig hana, string sid,
|
|
CancellationToken ct = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(config.SourceSchema))
|
|
{
|
|
logger.Error("Aurora: SourceSchema is not configured.");
|
|
return;
|
|
}
|
|
|
|
var hdbsql = locator.LocateHdbsql(hana.HdbsqlPath, sid, hana.InstanceNumber);
|
|
var threads = config.Threads > 0 ? config.Threads : Math.Max(1, Environment.ProcessorCount / 2);
|
|
var aurora = $"{config.SourceSchema}_AURORA";
|
|
var tmpDir = Path.Combine(config.BackupBasePath, $"{aurora}_TEMP_{DateTime.Now:yyyyMMdd_HHmmss}");
|
|
|
|
logger.Step($"Starting Aurora refresh: '{config.SourceSchema}' → '{aurora}'");
|
|
|
|
// 1. Drop old Aurora schema (ignore errors if it doesn't exist)
|
|
logger.Step($"Dropping old schema '{aurora}' (if exists)...");
|
|
await RunSql(hdbsql, config.AdminUserKey,
|
|
$"DROP SCHEMA \"{aurora}\" CASCADE;", sid, ct);
|
|
|
|
// 2. Prepare temp export directory
|
|
await RunAs(sid, $"mkdir -p \"{tmpDir}\"", ct);
|
|
|
|
// 3. Export source schema
|
|
logger.Step($"Exporting '{config.SourceSchema}' to temp dir...");
|
|
var exportResult = await RunSql(hdbsql, config.AdminUserKey,
|
|
$"EXPORT \"{config.SourceSchema}\".\"*\" AS BINARY INTO '{tmpDir}' WITH REPLACE THREADS {threads} NO DEPENDENCIES;",
|
|
sid, ct);
|
|
|
|
if (!exportResult.Success)
|
|
{
|
|
logger.Error($"Aurora export failed: {exportResult.StdErr}");
|
|
await ntfy.SendAsync("Aurora Failed", $"Export of '{config.SourceSchema}' FAILED.", ct);
|
|
await Cleanup(tmpDir, sid, ct);
|
|
return;
|
|
}
|
|
|
|
// 4. Import → rename
|
|
logger.Step($"Importing as '{aurora}'...");
|
|
var importResult = await RunSql(hdbsql, config.AdminUserKey,
|
|
$"IMPORT \"{config.SourceSchema}\".\"*\" AS BINARY FROM '{tmpDir}' WITH REPLACE RENAME SCHEMA \"{config.SourceSchema}\" TO \"{aurora}\" THREADS {threads};",
|
|
sid, ct);
|
|
|
|
if (!importResult.Success)
|
|
{
|
|
logger.Error($"Aurora import failed: {importResult.StdErr}");
|
|
await ntfy.SendAsync("Aurora Failed", $"Import-rename to '{aurora}' FAILED.", ct);
|
|
await Cleanup(tmpDir, sid, ct);
|
|
return;
|
|
}
|
|
|
|
// 5. Get original company name from CINF
|
|
logger.Step("Fetching company name from source schema...");
|
|
var nameResult = await RunSqlScalar(hdbsql, config.AdminUserKey,
|
|
$"SELECT \"CompnyName\" FROM \"{config.SourceSchema}\".\"CINF\";", sid, ct);
|
|
var companyName = string.IsNullOrWhiteSpace(nameResult) ? config.SourceSchema : nameResult;
|
|
var newName = $"AURORA - {companyName} - {DateTime.Now:yyyy-MM-dd}";
|
|
|
|
// 6. Update company name in CINF and OADM
|
|
logger.Step($"Setting company name to '{newName}'...");
|
|
await RunSql(hdbsql, config.AdminUserKey,
|
|
$"UPDATE \"{aurora}\".\"CINF\" SET \"CompnyName\" = '{newName}';", sid, ct);
|
|
await RunSql(hdbsql, config.AdminUserKey,
|
|
$"UPDATE \"{aurora}\".\"OADM\" SET \"CompnyName\" = '{newName}', \"PrintHeadr\" = '{newName}';", sid, ct);
|
|
|
|
// 7. Grant privileges
|
|
if (!string.IsNullOrWhiteSpace(config.AuroraUser))
|
|
{
|
|
logger.Step($"Granting ALL PRIVILEGES on '{aurora}' to '{config.AuroraUser}'...");
|
|
await RunSql(hdbsql, config.AdminUserKey,
|
|
$"GRANT ALL PRIVILEGES ON SCHEMA \"{aurora}\" TO \"{config.AuroraUser}\";", sid, ct);
|
|
}
|
|
|
|
// 8. Cleanup temp directory
|
|
await Cleanup(tmpDir, sid, ct);
|
|
|
|
logger.Success($"Aurora refresh of '{aurora}' completed!");
|
|
await ntfy.SendAsync("Aurora Complete", $"Aurora refresh of '{aurora}' completed successfully.", ct);
|
|
}
|
|
|
|
private async Task<ProcessResult> RunSql(
|
|
string hdbsql, string userKey, string sql, string sid, CancellationToken ct)
|
|
{
|
|
var tmpFile = Path.Combine("/tmp", $"ht_{Guid.NewGuid():N}.sql");
|
|
await File.WriteAllTextAsync(tmpFile, sql, ct);
|
|
var result = await switcher.RunAsAsync(sid,
|
|
$"\"{hdbsql}\" -U {userKey} -I \"{tmpFile}\" 2>&1", ct);
|
|
File.Delete(tmpFile);
|
|
return result;
|
|
}
|
|
|
|
private async Task<string> RunSqlScalar(
|
|
string hdbsql, string userKey, string sql, string sid, CancellationToken ct)
|
|
{
|
|
var tmpFile = Path.Combine("/tmp", $"ht_{Guid.NewGuid():N}.sql");
|
|
await File.WriteAllTextAsync(tmpFile, sql, ct);
|
|
var result = await switcher.RunAsAsync(sid,
|
|
$"\"{hdbsql}\" -U {userKey} -a -x -I \"{tmpFile}\" 2>&1", ct);
|
|
File.Delete(tmpFile);
|
|
return result.StdOut.Replace("\"", "").Trim();
|
|
}
|
|
|
|
private Task<ProcessResult> RunAs(string sid, string cmd, CancellationToken ct) =>
|
|
switcher.RunAsAsync(sid, cmd, ct);
|
|
|
|
private async Task Cleanup(string dir, string sid, CancellationToken ct)
|
|
{
|
|
logger.Step($"Cleaning up temp dir '{dir}'...");
|
|
await RunAs(sid, $"rm -rf \"{dir}\"", ct);
|
|
}
|
|
}
|