mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
fix reboot, metrics, teams notification
This commit is contained in:
@@ -556,8 +556,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
}
|
||||
});
|
||||
|
||||
// Only trigger self-destruct if configured
|
||||
if (_configuration.SelfDestructOnComplete)
|
||||
// Trigger self-destruct or reboot if configured
|
||||
if (_configuration.SelfDestructOnComplete || _configuration.RebootOnComplete)
|
||||
{
|
||||
Task.Run(() => HandleEnrollmentComplete());
|
||||
return; // Don't continue with normal event processing
|
||||
@@ -752,7 +752,28 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
}
|
||||
else if (_configuration.RebootOnComplete)
|
||||
{
|
||||
_logger.Info("RebootOnComplete enabled - initiating reboot");
|
||||
_logger.Info($"RebootOnComplete enabled - initiating reboot in {_configuration.RebootDelaySeconds}s");
|
||||
|
||||
// Emit reboot event into the timeline before shutdown
|
||||
EmitEvent(new EnrollmentEvent
|
||||
{
|
||||
SessionId = _configuration.SessionId,
|
||||
TenantId = _configuration.TenantId,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
EventType = "reboot_triggered",
|
||||
Severity = EventSeverity.Info,
|
||||
Source = "MonitoringService",
|
||||
Phase = EnrollmentPhase.Complete,
|
||||
Message = $"Reboot configured — triggering reboot in {_configuration.RebootDelaySeconds} seconds",
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "rebootDelaySeconds", _configuration.RebootDelaySeconds }
|
||||
}
|
||||
});
|
||||
|
||||
// Final upload to ensure the reboot event is sent
|
||||
await UploadEventsAsync();
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "shutdown.exe",
|
||||
|
||||
@@ -136,10 +136,13 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
}
|
||||
|
||||
// Update session status based on events
|
||||
// statusTransitioned = true only when this is the FIRST time the session reaches Succeeded/Failed
|
||||
// (UpdateSessionStatusAsync returns false if already in a terminal state — idempotency guard)
|
||||
bool statusTransitioned = false;
|
||||
if (completionEvent != null)
|
||||
{
|
||||
// Enrollment completed successfully - use event timestamp for accurate duration
|
||||
await _storageService.UpdateSessionStatusAsync(
|
||||
statusTransitioned = await _storageService.UpdateSessionStatusAsync(
|
||||
request.TenantId,
|
||||
request.SessionId,
|
||||
SessionStatus.Succeeded,
|
||||
@@ -147,7 +150,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
completedAt: completionEvent.Timestamp,
|
||||
earliestEventTimestamp: earliestEventTimestamp
|
||||
);
|
||||
_logger.LogInformation($"{sessionPrefix} Status: Succeeded");
|
||||
_logger.LogInformation("{SessionPrefix} Status: Succeeded (transitioned={Transitioned})", sessionPrefix, statusTransitioned);
|
||||
}
|
||||
else if (failureEvent != null)
|
||||
{
|
||||
@@ -156,7 +159,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
? $"{failureEvent.Message} ({failureEvent.Data["errorCode"]})"
|
||||
: failureEvent.Message;
|
||||
|
||||
await _storageService.UpdateSessionStatusAsync(
|
||||
statusTransitioned = await _storageService.UpdateSessionStatusAsync(
|
||||
request.TenantId,
|
||||
request.SessionId,
|
||||
SessionStatus.Failed,
|
||||
@@ -165,7 +168,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
completedAt: failureEvent.Timestamp,
|
||||
earliestEventTimestamp: earliestEventTimestamp
|
||||
);
|
||||
_logger.LogWarning($"{sessionPrefix} Status: Failed - {failureReason}");
|
||||
_logger.LogWarning("{SessionPrefix} Status: Failed - {FailureReason} (transitioned={Transitioned})", sessionPrefix, failureReason, statusTransitioned);
|
||||
}
|
||||
else if (lastPhaseChangeEvent != null)
|
||||
{
|
||||
@@ -228,7 +231,8 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
var updatedSession = await _storageService.GetSessionAsync(request.TenantId, request.SessionId);
|
||||
|
||||
// Send Teams notification on enrollment completion (fire-and-forget, non-fatal)
|
||||
if (completionEvent != null || failureEvent != null)
|
||||
// Only send when statusTransitioned=true to prevent duplicates on retry/double-upload
|
||||
if (statusTransitioned && (completionEvent != null || failureEvent != null))
|
||||
{
|
||||
var tenantConfig = await _configService.GetConfigurationAsync(request.TenantId);
|
||||
if (!string.IsNullOrEmpty(tenantConfig.TeamsWebhookUrl))
|
||||
|
||||
@@ -343,7 +343,8 @@ namespace AutopilotMonitor.Functions.Services
|
||||
var sessions = new List<SessionSummary>();
|
||||
|
||||
// Query all sessions without tenant filter
|
||||
var query = tableClient.QueryAsync<TableEntity>(maxPerPage: maxResults);
|
||||
// maxPerPage is capped at 1000 (Azure Table Storage limit); pagination is automatic
|
||||
var query = tableClient.QueryAsync<TableEntity>(maxPerPage: Math.Min(maxResults, 1000));
|
||||
|
||||
await foreach (var entity in query)
|
||||
{
|
||||
@@ -404,6 +405,18 @@ namespace AutopilotMonitor.Functions.Services
|
||||
var entity = await tableClient.GetEntityAsync<TableEntity>(tenantId, sessionId);
|
||||
var session = entity.Value;
|
||||
|
||||
// Idempotency: if the session is already in a terminal state (Succeeded/Failed),
|
||||
// do not overwrite it with another terminal state to prevent duplicate notifications.
|
||||
if (status == SessionStatus.Succeeded || status == SessionStatus.Failed)
|
||||
{
|
||||
var existingStatus = session.GetString("Status");
|
||||
if (existingStatus == SessionStatus.Succeeded.ToString() || existingStatus == SessionStatus.Failed.ToString())
|
||||
{
|
||||
_logger.LogInformation($"Session {sessionId} already in terminal state '{existingStatus}', skipping status update to '{status}'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update status
|
||||
session["Status"] = status.ToString();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user