diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs index 545c124b1..08ca42ce0 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs @@ -10,10 +10,14 @@ namespace AzureIoTHub.Portal.Infrastructure.Jobs.AWS using Amazon.GreengrassV2.Model; using Amazon.IoT; using Amazon.IoT.Model; + using Amazon.SecretsManager.Model; using AutoMapper; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Application.Services.AWS; using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Models.v10; using Microsoft.Extensions.Logging; using Quartz; using Quartz.Util; @@ -30,6 +34,8 @@ public class SyncGreenGrassDevicesJob : IJob private readonly IDeviceTagValueRepository deviceTagValueRepository; private readonly IAmazonIoT amazonIoTClient; private readonly IAmazonGreengrassV2 amazonGreenGrass; + private readonly IConfigService configService; + private readonly IAWSExternalDeviceService awsExternalDevicesService; public SyncGreenGrassDevicesJob( ILogger logger, @@ -39,7 +45,9 @@ public SyncGreenGrassDevicesJob( IEdgeDeviceModelRepository edgeDeviceModelRepository, IDeviceTagValueRepository deviceTagValueRepository, IAmazonIoT amazonIoTClient, - IAmazonGreengrassV2 amazonGreenGrass) + IAmazonGreengrassV2 amazonGreenGrass, + IConfigService configService, + IAWSExternalDeviceService awsExternalDevicesService) { this.mapper = mapper; this.unitOfWork = unitOfWork; @@ -48,6 +56,8 @@ public SyncGreenGrassDevicesJob( this.deviceTagValueRepository = deviceTagValueRepository; this.amazonIoTClient = amazonIoTClient; this.amazonGreenGrass = amazonGreenGrass; + this.configService = configService; + this.awsExternalDevicesService = awsExternalDevicesService; this.logger = logger; } @@ -96,8 +106,31 @@ private async Task SyncGreenGrassDevicesAsEdgeDevices() continue; } + //Map with EdgeDevice + var edgeDevice = this.mapper.Map(thing); + edgeDevice.DeviceModelId = edgeDeviceModel.Id; + //EdgeDevices properties that are not present in the thing + try + { + var modules = await this.configService.GetConfigModuleList(edgeDevice.DeviceModelId); + edgeDevice.NbDevices = await this.awsExternalDevicesService.GetEdgeDeviceNbDevices(this.mapper.Map(edgeDevice)); + edgeDevice.NbModules = modules.Count; + var coreDevice = await amazonGreenGrass.GetCoreDeviceAsync(new GetCoreDeviceRequest() { CoreDeviceThingName = thing.ThingName }); + if (coreDevice.HttpStatusCode != HttpStatusCode.OK) + { + this.logger.LogWarning($"Cannot import Greengrass device '{thing.ThingName}' due to an error retrieving core device in the Amazon IoT Data API : {coreDevice.HttpStatusCode}"); + continue; + } + edgeDevice.ConnectionState = coreDevice.Status == CoreDeviceStatus.HEALTHY ? "Connected" : "Disconnected"; + } + catch (Exception e) + { + this.logger.LogWarning($"Cannot import Greengrass device '{thing.ThingName}' due to an error retrieving Greengrass device properties in the Amazon IoT Data API.", e); + continue; + } + //Create or update the Edge Device - await CreateOrUpdateGreenGrassDevice(thing, edgeDeviceModel); + await CreateOrUpdateGreenGrassDevice(edgeDevice); } foreach (var item in (await this.edgeDeviceRepository.GetAllAsync( @@ -141,11 +174,9 @@ private async Task> GetAllGreenGrassDevices() return devices; } - private async Task CreateOrUpdateGreenGrassDevice(DescribeThingResponse greengrassDevice, EdgeDeviceModel edgeModelDevice) + private async Task CreateOrUpdateGreenGrassDevice(EdgeDevice edgeDevice) { - var edgeDevice = this.mapper.Map(greengrassDevice); var edgeDeviceEntity = await this.edgeDeviceRepository.GetByIdAsync(edgeDevice.Id, d => d.Tags); - edgeDevice.DeviceModelId = edgeModelDevice.Id; if (edgeDeviceEntity == null) { diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs index 0ce7a2aae..e0a55dea9 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs @@ -5,6 +5,7 @@ namespace AzureIoTHub.Portal.Infrastructure.Services { using System; using System.Threading.Tasks; + using Amazon.GreengrassV2; using Amazon.IoT.Model; using AutoMapper; using AzureIoTHub.Portal.Application.Managers; @@ -137,6 +138,7 @@ public async Task GetEdgeDevice(string edgeDeviceId) deviceDto.Modules = await this.configService.GetConfigModuleList(model.ExternalIdentifier!); deviceDto.NbDevices = await this.awsExternalDevicesService.GetEdgeDeviceNbDevices(deviceDto); deviceDto.NbModules = deviceDto.Modules.Count; + deviceDto.ConnectionState = deviceDto.RuntimeResponse == CoreDeviceStatus.HEALTHY ? "Connected" : "Disconnected"; return deviceDto; } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs index b9816d834..71ce10f32 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs @@ -14,6 +14,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS using Amazon.IoT; using Amazon.IoT.Model; using AutoFixture; + using AzureIoTHub.Portal.Application.Services.AWS; + using AzureIoTHub.Portal.Application.Services; using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Repositories; @@ -23,6 +25,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS using Moq; using NUnit.Framework; using Quartz; + using AzureIoTHub.Portal.Models.v10; public class SyncGreenGrassDevicesJobTests : BackendUnitTest { @@ -34,6 +37,8 @@ public class SyncGreenGrassDevicesJobTests : BackendUnitTest private Mock mockDeviceRepository; private Mock mockDeviceModelRepository; private Mock mockDeviceTagValueRepository; + private Mock mockConfigService; + private Mock mockAwsExternalDevicesService; public override void Setup() { @@ -45,6 +50,8 @@ public override void Setup() this.mockDeviceTagValueRepository = MockRepository.Create(); this.amazonIoTClient = MockRepository.Create(); this.amazonGreenGrass = MockRepository.Create(); + this.mockConfigService = MockRepository.Create(); + this.mockAwsExternalDevicesService = MockRepository.Create(); _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceRepository.Object); @@ -52,6 +59,8 @@ public override void Setup() _ = ServiceCollection.AddSingleton(this.mockDeviceTagValueRepository.Object); _ = ServiceCollection.AddSingleton(this.amazonIoTClient.Object); _ = ServiceCollection.AddSingleton(this.amazonGreenGrass.Object); + _ = ServiceCollection.AddSingleton(this.mockConfigService.Object); + _ = ServiceCollection.AddSingleton(this.mockAwsExternalDevicesService.Object); _ = ServiceCollection.AddSingleton(); @@ -104,6 +113,15 @@ public async Task ExecuteNewEdgeDeviceEdgeDeviceCreated() .Setup(x => x.GetByNameAsync(newDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(newDevice.Id, d => d.Tags)) .ReturnsAsync((EdgeDevice)null); @@ -184,6 +202,15 @@ public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) .ReturnsAsync(existingDevice); @@ -265,6 +292,15 @@ public async Task ExecuteExistingEdgeDeviceWithOlderVersionEdgeDeviceNotUpdated( .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) .ReturnsAsync(existingDevice); @@ -338,6 +374,72 @@ public async Task ExecuteNewEdgeDeviceWithDescribeThingErrorSkipped() MockRepository.VerifyAll(); } + [Test] + public async Task ExecuteNewEdgeDeviceWithGetCoreDeviceErrorSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 1 + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + ThingTypeName = existingDevice.DeviceModel.Name, + Version = 1, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.RequestTimeout }); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + [Test] public async Task ExecuteNewDeviceWithoutThingTypeSkipped() {