Refactor code structure for improved readability and maintainability
This commit is contained in:
315
Services/QueueJobService.cs
Normal file
315
Services/QueueJobService.cs
Normal file
@@ -0,0 +1,315 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.SelfService.Portal.Core.API.Context;
|
||||
using Microsoft.SelfService.Portal.Core.API.Interfaces;
|
||||
using Microsoft.SelfService.Portal.Core.API.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.SelfService.Portal.Core.API.Services
|
||||
{
|
||||
public class QueueJobService : IQueueJobService
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
|
||||
public QueueJobService(DataContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public Guid EnqueueTemplateJsonChanged(Guid templateId, string oldJsonData, string newJsonData)
|
||||
{
|
||||
var deployments = _context.Deployments
|
||||
.AsNoTracking()
|
||||
.Include(deployment => deployment.DeploymentGroup)
|
||||
.Where(deployment => deployment.DeploymentGroup.TemplateId == templateId)
|
||||
.ToList();
|
||||
|
||||
var payload = new
|
||||
{
|
||||
TemplateId = templateId,
|
||||
OldJsonData = oldJsonData,
|
||||
NewJsonData = newJsonData,
|
||||
TargetCount = deployments.Count,
|
||||
Created = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var queueJob = new QueueJobModel
|
||||
{
|
||||
Type = QueueJobType.TemplateJsonChanged,
|
||||
Status = QueueJobStatus.Pending,
|
||||
PayloadJson = JsonSerializer.Serialize(payload),
|
||||
Targets = deployments.Select(deployment => new QueueJobTargetModel
|
||||
{
|
||||
VirtualMachineId = deployment.VirtualMachineId,
|
||||
DeploymentGroupId = deployment.DeploymentGroupId,
|
||||
TemplateId = templateId,
|
||||
Status = QueueJobStatus.Pending
|
||||
}).ToList()
|
||||
};
|
||||
queueJob.Steps = BuildDefaultJobSteps();
|
||||
|
||||
_context.QueueJobs.Add(queueJob);
|
||||
_context.SaveChanges();
|
||||
|
||||
return queueJob.Id;
|
||||
}
|
||||
|
||||
public Guid EnqueueDeploymentRequest(Guid deploymentGroupId, ICollection<Guid> virtualMachineIds, string jsonData)
|
||||
{
|
||||
var deploymentGroup = _context.DeploymentGroups
|
||||
.AsNoTracking()
|
||||
.Include(group => group.Template)
|
||||
.ThenInclude(template => template.DeploymentRule)
|
||||
.ThenInclude(rule => rule.Steps)
|
||||
.FirstOrDefault(group => group.Id == deploymentGroupId);
|
||||
|
||||
if (deploymentGroup == null)
|
||||
{
|
||||
throw new InvalidOperationException("DeploymentGroup does not exist.");
|
||||
}
|
||||
|
||||
var templateId = deploymentGroup.TemplateId;
|
||||
var resolvedVirtualMachineIds = virtualMachineIds
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (resolvedVirtualMachineIds.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No target VirtualMachines provided.");
|
||||
}
|
||||
|
||||
var existingVirtualMachines = _context.VirtualMachines
|
||||
.AsNoTracking()
|
||||
.Where(virtualMachine => resolvedVirtualMachineIds.Contains(virtualMachine.Id))
|
||||
.Select(virtualMachine => virtualMachine.Id)
|
||||
.ToHashSet();
|
||||
|
||||
var missingVirtualMachines = resolvedVirtualMachineIds
|
||||
.Where(virtualMachineId => !existingVirtualMachines.Contains(virtualMachineId))
|
||||
.ToList();
|
||||
|
||||
if (missingVirtualMachines.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Unknown VirtualMachine IDs: {string.Join(", ", missingVirtualMachines)}");
|
||||
}
|
||||
|
||||
foreach (var virtualMachineId in resolvedVirtualMachineIds)
|
||||
{
|
||||
var deployment = _context.Deployments
|
||||
.FirstOrDefault(existing =>
|
||||
existing.DeploymentGroupId == deploymentGroupId
|
||||
&& existing.VirtualMachineId == virtualMachineId);
|
||||
|
||||
if (deployment == null)
|
||||
{
|
||||
deployment = new DeploymentModel
|
||||
{
|
||||
DeploymentGroupId = deploymentGroupId,
|
||||
VirtualMachineId = virtualMachineId,
|
||||
Status = QueueJobStatus.Pending,
|
||||
JSONData = jsonData
|
||||
};
|
||||
|
||||
_context.Deployments.Add(deployment);
|
||||
}
|
||||
else
|
||||
{
|
||||
deployment.Status = QueueJobStatus.Pending;
|
||||
deployment.JSONData = jsonData;
|
||||
}
|
||||
}
|
||||
|
||||
var payload = new
|
||||
{
|
||||
DeploymentGroupId = deploymentGroupId,
|
||||
TemplateId = templateId,
|
||||
VirtualMachineIds = resolvedVirtualMachineIds,
|
||||
JsonData = jsonData,
|
||||
TargetCount = resolvedVirtualMachineIds.Count,
|
||||
Created = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var queueJob = new QueueJobModel
|
||||
{
|
||||
Type = QueueJobType.DeploymentRequested,
|
||||
Status = QueueJobStatus.Pending,
|
||||
PayloadJson = JsonSerializer.Serialize(payload),
|
||||
RuleSnapshotJson = deploymentGroup.Template?.DeploymentRuleId.HasValue == true
|
||||
? SerializeRuleSnapshot(deploymentGroup.Template!.DeploymentRule)
|
||||
: null,
|
||||
Targets = resolvedVirtualMachineIds.Select(virtualMachineId => new QueueJobTargetModel
|
||||
{
|
||||
VirtualMachineId = virtualMachineId,
|
||||
DeploymentGroupId = deploymentGroupId,
|
||||
TemplateId = templateId,
|
||||
Status = QueueJobStatus.Pending
|
||||
}).ToList()
|
||||
};
|
||||
queueJob.Steps = BuildDeploymentSteps(deploymentGroup.Template?.DeploymentRule);
|
||||
|
||||
_context.QueueJobs.Add(queueJob);
|
||||
_context.SaveChanges();
|
||||
|
||||
return queueJob.Id;
|
||||
}
|
||||
|
||||
public bool RetryQueueJob(Guid queueJobId)
|
||||
{
|
||||
var queueJob = _context.QueueJobs
|
||||
.Include(job => job.Targets)
|
||||
.FirstOrDefault(job => job.Id == queueJobId);
|
||||
|
||||
if (queueJob == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
queueJob.Status = QueueJobStatus.Pending;
|
||||
queueJob.ErrorMessage = null;
|
||||
queueJob.Finished = null;
|
||||
queueJob.LockedUntil = null;
|
||||
queueJob.LockedBy = null;
|
||||
queueJob.Steps ??= new List<QueueJobStepModel>();
|
||||
|
||||
foreach (var target in queueJob.Targets)
|
||||
{
|
||||
if (target.Status == QueueJobStatus.Failed || target.Status == QueueJobStatus.Cancelled)
|
||||
{
|
||||
target.Status = QueueJobStatus.Pending;
|
||||
target.ErrorMessage = null;
|
||||
}
|
||||
}
|
||||
foreach (var step in queueJob.Steps)
|
||||
{
|
||||
if (step.Status == QueueJobStatus.Failed
|
||||
|| step.Status == QueueJobStatus.Cancelled
|
||||
|| step.Status == QueueJobStatus.WaitingForApproval
|
||||
|| step.Status == QueueJobStatus.Rejected)
|
||||
{
|
||||
step.Status = QueueJobStatus.Pending;
|
||||
step.ApprovedAt = null;
|
||||
step.ApprovedBy = null;
|
||||
step.ApprovalComment = null;
|
||||
}
|
||||
}
|
||||
|
||||
return _context.SaveChanges() > 0;
|
||||
}
|
||||
|
||||
public bool ApproveQueueJobStep(Guid queueJobStepId, string approvedBy, string? comment)
|
||||
{
|
||||
var step = _context.QueueJobSteps
|
||||
.Include(existing => existing.QueueJob)
|
||||
.FirstOrDefault(existing => existing.Id == queueJobStepId);
|
||||
|
||||
if (step == null || step.StepType != QueueJobStepType.Approval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
step.Status = QueueJobStatus.Succeeded;
|
||||
step.ApprovedAt = DateTime.UtcNow;
|
||||
step.ApprovedBy = approvedBy;
|
||||
step.ApprovalComment = comment;
|
||||
step.QueueJob.Status = QueueJobStatus.Pending;
|
||||
step.QueueJob.LockedUntil = null;
|
||||
step.QueueJob.LockedBy = null;
|
||||
step.QueueJob.ErrorMessage = null;
|
||||
|
||||
return _context.SaveChanges() > 0;
|
||||
}
|
||||
|
||||
public bool RejectQueueJobStep(Guid queueJobStepId, string approvedBy, string? comment)
|
||||
{
|
||||
var step = _context.QueueJobSteps
|
||||
.Include(existing => existing.QueueJob)
|
||||
.FirstOrDefault(existing => existing.Id == queueJobStepId);
|
||||
|
||||
if (step == null || step.StepType != QueueJobStepType.Approval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
step.Status = QueueJobStatus.Rejected;
|
||||
step.ApprovedAt = DateTime.UtcNow;
|
||||
step.ApprovedBy = approvedBy;
|
||||
step.ApprovalComment = comment;
|
||||
step.QueueJob.Status = QueueJobStatus.Rejected;
|
||||
step.QueueJob.Finished = DateTime.UtcNow;
|
||||
step.QueueJob.LockedUntil = null;
|
||||
step.QueueJob.LockedBy = null;
|
||||
step.QueueJob.ErrorMessage = comment ?? "Deployment step rejected.";
|
||||
|
||||
return _context.SaveChanges() > 0;
|
||||
}
|
||||
|
||||
private static List<QueueJobStepModel> BuildDefaultJobSteps()
|
||||
{
|
||||
return new List<QueueJobStepModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
SortOrder = 1,
|
||||
Name = "Provision",
|
||||
StepType = QueueJobStepType.Provision,
|
||||
Status = QueueJobStatus.Pending
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<QueueJobStepModel> BuildDeploymentSteps(DeploymentRuleModel? deploymentRule)
|
||||
{
|
||||
if (deploymentRule?.Steps == null || deploymentRule.Steps.Count == 0)
|
||||
{
|
||||
return BuildDefaultJobSteps();
|
||||
}
|
||||
|
||||
var steps = deploymentRule.Steps
|
||||
.OrderBy(step => step.SortOrder)
|
||||
.Select(step => new QueueJobStepModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
SortOrder = step.SortOrder,
|
||||
Name = step.Name,
|
||||
StepType = step.RequiresApproval ? QueueJobStepType.Approval : step.StepType,
|
||||
Status = QueueJobStatus.Pending,
|
||||
MetadataJson = step.MetadataJson
|
||||
})
|
||||
.ToList();
|
||||
|
||||
for (var i = 1; i < steps.Count; i++)
|
||||
{
|
||||
steps[i].DependsOnQueueJobStepId = steps[i - 1].Id;
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
private static string? SerializeRuleSnapshot(DeploymentRuleModel? rule)
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var snapshot = new
|
||||
{
|
||||
rule.Id,
|
||||
rule.Name,
|
||||
Steps = rule.Steps
|
||||
.OrderBy(step => step.SortOrder)
|
||||
.Select(step => new
|
||||
{
|
||||
step.Id,
|
||||
step.SortOrder,
|
||||
step.Name,
|
||||
step.StepType,
|
||||
step.RequiresApproval,
|
||||
step.MetadataJson
|
||||
})
|
||||
};
|
||||
|
||||
return JsonSerializer.Serialize(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user