Files
parr_api/PARR.DAL/Repositories/RobotConfigurationRepository.cs

255 lines
13 KiB
C#
Raw Normal View History

using InfluxDB.Client.Api.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.DAL.Context;
using PARR.DAL.Repositories.Base;
using PARR.Domain.Entities;
using PARR.Domain.Entities.RobotEntities;
using PARR.Domain.Enums;
using PARR.Domain.Settings;
namespace PARR.DAL.Repositories
{
internal class RobotConfigurationRepository : BaseRepository<RobotConfiguration>, IRobotConfigurationRepository
{
private readonly SettingsFromDb settingsFromDb;
public RobotConfigurationRepository(DataContext dataContext, ILogger<RobotConfigurationRepository> logger, SettingsFromDb settingsFromDb) : base(logger, dataContext)
{
this.settingsFromDb = settingsFromDb;
}
public void ChangeTaskStatus(TaskStatusEnum taskStatus, RobotConfiguration configuration)
{
configuration.TaskStatusCode = (int)taskStatus;
switch (taskStatus)
{
case TaskStatusEnum.Creating:
ChangeRobotStatus(RobotStatusEnum.Wait, configuration);
break;
case TaskStatusEnum.Updating:
ChangeRobotStatus(RobotStatusEnum.Wait, configuration);
break;
case TaskStatusEnum.Ok:
ChangeRobotStatus(RobotStatusEnum.Complete, configuration);
break;
default:
break;
}
}
public bool SetUpdateTaskStatusIfAllow(RobotConfiguration configuration)
{
var updatingStatus = TaskStatusEnum.Updating;
// ставить статус Updating можно только если текущий статус == Ok
if (configuration.TaskStatusCode != (int)TaskStatusEnum.Ok)
{
int taskStatusValue = configuration.TaskStatusCode;
string taskStatusName = Enum.IsDefined(typeof(TaskStatusEnum), taskStatusValue)
? ((TaskStatusEnum)taskStatusValue).ToString()
: $"Unknown ({taskStatusValue})";
_logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как текущий статус {currentStatus}",
updatingStatus, configuration.Id, configuration.TemplateId, taskStatusName);
return false;
}
// Если хотим поставить Updating у шаблона, можно ставить только в том случае, если у шаблона уже есть ScheduleEsppId
if (configuration.RobotCode == (int)RobotsEnum.TemplateOrder)
{
// есть ли связь у config с templetes, может инклуда нет, мало ли
if (configuration.Template == null)
{
_logger.LogWarning("При изменении статуса задания на обновление шаблона, не смог проверить наличае ScheduleEsppId, так как нет Include с Templates. Пропустил эту проверку. configurationId: {configurationId}", configuration.Id);
}
else
{
if (configuration.Template.ScheduleEsppId == null)
{
_logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как у шаблона отсутсвтует ScheduleEsppId=null",
updatingStatus, configuration.Id, configuration.TemplateId);
return false;
}
}
}
// Статус ОК, можно ставить Updating
ChangeTaskStatus(updatingStatus, configuration);
_logger.LogInformation("Установлен статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}", updatingStatus, configuration.Id, configuration.TemplateId);
return true;
}
public void ChangeRobotStatus(RobotStatusEnum robotStatus, RobotConfiguration configuration)
{
configuration.RobotStatusCode = (int)robotStatus;
switch (robotStatus)
{
case RobotStatusEnum.InProgress:
configuration.AttemptsNumber++;
configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow;
break;
case RobotStatusEnum.Error:
break;
case RobotStatusEnum.Complete:
configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow;
break;
case RobotStatusEnum.Wait:
configuration.LastRobotStatusUpdated = null;
configuration.AttemptsNumber = 0;
break;
default:
break;
}
}
public void SetErrorRobotStatusAndMaxAttempts(RobotConfiguration configuration)
{
ChangeRobotStatus(RobotStatusEnum.Error, configuration);
configuration.AttemptsNumber = settingsFromDb.RobotAttemptsNumber;
configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow;
}
public async Task<bool> SetInProgressStatusAsync(Guid id)
{
// Если задание все еще в статусе Wait (RobotStatusEnum.Wait), установить ему статус "InProgress"
var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
// Выполняем атомарный UPDATE напрямую в базе данных
var affectedRows = await EntitySet.Where(t => t.Id == id
&& (
// Задания в статусе ожидание
t.RobotStatusCode == (int)RobotStatusEnum.Wait
|| (
// Задание в статусе в работе, но они зависли
t.RobotStatusCode == (int)RobotStatusEnum.InProgress
&& t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
&& t.LastRobotStatusUpdated < endDate
)
)
)
.ExecuteUpdateAsync(s => s
.SetProperty(t => t.RobotStatusCode, (int)RobotStatusEnum.InProgress)
.SetProperty(t => t.LastRobotStatusUpdated, DateTimeOffset.UtcNow)
.SetProperty(t => t.AttemptsNumber, t => t.AttemptsNumber + 1)
);
return affectedRows > 0;
}
public RobotConfiguration GetFromTemplateByRobotCode(RobotsEnum robotsEnum, Template template)
{
var config = template.RobotConfigurations.FirstOrDefault(t => t.RobotCode == (int)robotsEnum);
if (config == null)
{
_logger.LogError($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}");
throw new Exception($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}");
}
return config;
}
// Поиск невыполненных заданий и установка им статуса ошибки робота
public async Task MarkExpiredTasksAsFailedAsync(int robotAttemptsNumber, TimeSpan robotWaitTime)
{
//Ищем `RobotStatusCode` = 22 и `LastStatusUpdated` истекло и `AttemptsNumber` >= допустимого значения из настроек,
//ставим всем этим записям `RobotStatusCode`= 33
var endDate = DateTimeOffset.UtcNow.Add(-robotWaitTime);
var expiredConfigs = await EntitySet.Where(t =>
t.RobotStatusCode == (int)RobotStatusEnum.InProgress
&& t.AttemptsNumber >= robotAttemptsNumber
&& t.LastRobotStatusUpdated <= endDate
).ToListAsync();
if (!expiredConfigs.Any())
return;
foreach (var item in expiredConfigs)
{
ChangeRobotStatus(RobotStatusEnum.Error, item);
_logger.LogInformation("Устанавливаю статус RobotStatus: {RobotStatus} для RobotConfigurationId: {RobotConfigurationId}", RobotStatusEnum.Error, item.Id);
}
#region Ищем, есть ли связанные шаблоны, которые должны переименоваться, им тоже нужно установить статус ошибки, но только для Шаблонов
// Проактивная обработка связанных шаблонов переименования Old->New
// Если старый шаблон умен, мы должны сразу убить (!!!замочить!!!) и новый (целевой), чтобы он не висел вечно в ожидании.
var expiredTemplateIds = expiredConfigs
.Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder)
.Select(t => t.TemplateId)
.Distinct()
.ToList();
if (expiredTemplateIds.Any())
{
// Находим OldName для этих шаблонов из таблицы переименований.
var oldNamesToFail = await EntityContext.Templates
.Where(t => expiredTemplateIds.Contains(t.Id) && t.TemplateRenamePending != null)
.Select(t => t.TemplateRenamePending!.OldName)
.Distinct()
.ToListAsync();
// Находим целевые (новые задачи), имена которых совпадают с найденными OldName
if (oldNamesToFail.Any())
{
var targetConfigs = await EntitySet
.Where(t =>
t.RobotCode == (int)RobotsEnum.TemplateOrder
&& t.RobotStatusCode != (int)RobotStatusEnum.Error // Не трогаем те, что уже в ошибке
&& oldNamesToFail.Contains(t.Template!.Name)
).ToListAsync();
foreach (var item in targetConfigs)
{
SetErrorRobotStatusAndMaxAttempts(item);
// Пишем в лог роботу
var history = new RobotHistory
{
Id = Guid.NewGuid(),
HistoryLevel = (int)RobotStatusEnum.Error,
TaskStatusCode = item.TaskStatusCode,
RobotConfigurationId = item.Id,
RobotIp = null,
RobotId = ParrComponentsEnum.Api.ToString(),
RobotMessage = "[RobotConfigurationRepository] Установлен статус ошибки, так как не переименован связанный шаблон"
};
// Синхронный Add работает быстрее и безопаснее внутри цикла
EntityContext.RobotHistories.Add(history);
_logger.LogInformation("Проактивно установлен статус {Status} для целевого задания RobotConfigurationID: {Id} из-за ошибки старого шаблона.", RobotStatusEnum.Error, item.Id);
}
}
}
#endregion
var result = await CommitAsync();
if (!result)
_logger.LogError("Ошибка при сохранении изменений RobotStatus для просроченных заданий. Откат транзакции.");
//else
// logger.LogInformation("Успешно обработано и переведено в статус Ошибки просроченных заданий: {Count} шт.", configObjs.Count + linksCount);
}
}
}