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

255 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}