Add NTFS automatic ensure timeout diagnostics

This commit is contained in:
Meik
2026-07-16 12:42:37 +02:00
parent e7fb041ff5
commit 0b1dbd7925
3 changed files with 201 additions and 12 deletions

View File

@@ -5,6 +5,7 @@ using C4IT_IAM_Engine;
using LiamAD;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -77,6 +78,9 @@ namespace LiamWorkflowActivities
public static class LiamWorkflowRuntime
{
private const string AutomaticNtfsEnsureTimeoutSecondsKey = "NtfsAutomaticEnsureTimeoutSeconds";
private const int DefaultAutomaticNtfsEnsureTimeoutSeconds = 300;
public static async Task<GetDataAreasOperationResult> GetDataAreasFromProviderAsync(cLiamProviderBase provider, string configurationId = null, bool? simulateConfiguredNtfsPermissionEnsure = null)
{
var result = new GetDataAreasOperationResult();
@@ -422,11 +426,19 @@ namespace LiamWorkflowActivities
var allowFolderEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroups");
var allowSharePathEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroupsForShares");
var allowTraverseEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsTraverseGroups");
var ensureTimeoutSeconds = GetAdditionalConfigurationInt32(
provider,
AutomaticNtfsEnsureTimeoutSecondsKey,
DefaultAutomaticNtfsEnsureTimeoutSeconds,
0);
var ensureTimeout = ensureTimeoutSeconds > 0
? TimeSpan.FromSeconds(ensureTimeoutSeconds)
: TimeSpan.Zero;
if (!allowFolderEnsure && !allowSharePathEnsure && !allowTraverseEnsure)
return true;
LogEntry(
$"Automatic NTFS ensure configured. PermissionGroups={allowFolderEnsure}, PermissionGroupsForShares={allowSharePathEnsure}, TraverseGroups={allowTraverseEnsure}, DataAreas={dataAreas.Count}, WhatIf={simulateOnly}",
$"Automatic NTFS ensure configured. PermissionGroups={allowFolderEnsure}, PermissionGroupsForShares={allowSharePathEnsure}, TraverseGroups={allowTraverseEnsure}, DataAreas={dataAreas.Count}, WhatIf={simulateOnly}, TimeoutSeconds={ensureTimeoutSeconds}",
LogLevels.Debug);
if (allowFolderEnsure || allowSharePathEnsure)
@@ -449,15 +461,19 @@ namespace LiamWorkflowActivities
ResultToken ensureResult;
try
{
ensureResult = await ntfsProvider.EnsureMissingPermissionGroupsAsync(
ensureResult = await RunAutomaticNtfsEnsureOperationAsync(
folderPath,
null,
null,
null,
null,
allowSharePathEnsure,
ensureTraverseInPermissionPhase,
simulateOnly);
"permission group ensure",
() => ntfsProvider.EnsureMissingPermissionGroupsAsync(
folderPath,
null,
null,
null,
null,
allowSharePathEnsure,
ensureTraverseInPermissionPhase,
simulateOnly),
ensureTimeout);
}
catch (Exception ex)
{
@@ -477,6 +493,12 @@ namespace LiamWorkflowActivities
if (ensureResult.resultErrorId != 0)
{
if (IsAutomaticNtfsEnsureTimeout(ensureResult))
{
SetAutomaticEnsureTimeoutError(result, folderPath, ensureResult, "permission group ensure");
return false;
}
LogEntry(
$"Automatic NTFS permission group ensure skipped for '{folderPath}' after resultErrorId={ensureResult.resultErrorId}. ResultMessage='{ensureResult.resultMessage ?? string.Empty}'",
LogLevels.Warning);
@@ -514,10 +536,14 @@ namespace LiamWorkflowActivities
ResultToken ensureResult;
try
{
ensureResult = await ntfsProvider.EnsureTraverseGroupsAsync(
ensureResult = await RunAutomaticNtfsEnsureOperationAsync(
folderPath,
null,
simulateOnly);
"traverse group ensure",
() => ntfsProvider.EnsureTraverseGroupsAsync(
folderPath,
null,
simulateOnly),
ensureTimeout);
}
catch (Exception ex)
{
@@ -537,6 +563,12 @@ namespace LiamWorkflowActivities
if (ensureResult.resultErrorId != 0)
{
if (IsAutomaticNtfsEnsureTimeout(ensureResult))
{
SetAutomaticEnsureTimeoutError(result, folderPath, ensureResult, "traverse group ensure");
return false;
}
LogEntry(
$"Automatic NTFS traverse group ensure skipped for '{folderPath}' after resultErrorId={ensureResult.resultErrorId}. ResultMessage='{ensureResult.resultMessage ?? string.Empty}'",
LogLevels.Warning);
@@ -557,6 +589,55 @@ namespace LiamWorkflowActivities
return true;
}
private static async Task<ResultToken> RunAutomaticNtfsEnsureOperationAsync(
string folderPath,
string operationLabel,
Func<Task<ResultToken>> operation,
TimeSpan timeout)
{
var operationTask = Task.Run(operation);
#pragma warning disable 4014
// Fire-and-forget observer for exceptions that happen after a timeout.
operationTask.ContinueWith(
task => LogException(task.Exception),
TaskContinuationOptions.OnlyOnFaulted);
#pragma warning restore 4014
if (timeout <= TimeSpan.Zero)
return await operationTask;
var completedTask = await Task.WhenAny(operationTask, Task.Delay(timeout));
if (completedTask == operationTask)
return await operationTask;
var message =
$"Automatic NTFS {operationLabel} timed out for '{folderPath}' after {timeout.TotalSeconds:F0}s. " +
"The workflow will stop automatic NTFS ensure processing for this run. " +
"The underlying NTFS/AD operation may still finish in the worker process.";
LogEntry(message, LogLevels.Error);
return new ResultToken("AutomaticNtfsEnsureTimeout")
{
resultErrorId = 30010,
resultMessage = message
};
}
private static bool IsAutomaticNtfsEnsureTimeout(ResultToken ensureResult)
{
return ensureResult?.resultErrorId == 30010;
}
private static void SetAutomaticEnsureTimeoutError(
GetDataAreasOperationResult result,
string folderPath,
ResultToken ensureResult,
string operationLabel)
{
result.ErrorCode = "WF_GET_DATAAREAS_ENSURE_NTFS_GROUPS_TIMEOUT";
result.ErrorMessage = ensureResult?.resultMessage
?? $"Automatic NTFS {operationLabel} timed out for '{folderPath}'.";
}
private static NtfsAutomaticEnsurePreviewEntry MapAutomaticEnsurePreview(string folderPath, ResultToken ensureResult)
{
return new NtfsAutomaticEnsurePreviewEntry
@@ -662,6 +743,33 @@ namespace LiamWorkflowActivities
|| rawValue.Equals("yes", StringComparison.OrdinalIgnoreCase);
}
private static int GetAdditionalConfigurationInt32(cLiamProviderBase provider, string key, int defaultValue, int minValue)
{
if (provider?.AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
return defaultValue;
if (!provider.AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
return defaultValue;
if (!int.TryParse(rawValue.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue))
{
LogEntry(
$"AdditionalConfiguration '{key}' has invalid integer value '{rawValue}'. Defaulting to {defaultValue}.",
LogLevels.Warning);
return defaultValue;
}
if (parsedValue < minValue)
{
LogEntry(
$"AdditionalConfiguration '{key}' value {parsedValue} is below minimum {minValue}. Defaulting to {defaultValue}.",
LogLevels.Warning);
return defaultValue;
}
return parsedValue;
}
private static bool IsWorkflowWhatIfEnabled(cLiamProviderBase provider)
{
return IsAdditionalConfigurationEnabled(provider, "WhatIf");