From d99d00134334618c34eccaa589db82253057a94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?DELAGE=20Rapha=C3=ABl?= <36408929+delager@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:13:24 +0200 Subject: [PATCH] Refactor the AWS External Device Service (#2185) * #2110 Refactor AWS External Device - Remove AWS Exterrnal Device - Calls to AWS APIs in the respective services - Add methods to mutualize in IExternalDeviceService * #2110 Add missing TU * #2110 Add AWSExternalDeviceService TU --- .../Mappers/DeviceModelProfile.cs | 7 + .../Services/AWS/IAWSExternalDeviceService.cs | 31 - .../Services/IExternalDeviceService.cs | 5 + .../Jobs/AWS/SyncThingTypesJob.cs | 11 +- .../Jobs/AWS/SyncThingsJob.cs | 19 +- .../Services/AWS/AWSDevicePropertyService.cs | 34 +- .../Services/AWS/AWSDeviceService.cs | 79 ++- .../Services/AWS/AWSEdgeDevicesService.cs | 52 +- .../Services/AWS/AWSExternalDeviceService.cs | 221 ------- .../Services/AwsExternalDeviceService.cs | 87 ++- .../Startup/AWSServiceCollectionExtension.cs | 2 - .../Services/ExternalDeviceService.cs | 10 + .../Jobs/AWS/SyncThingTypesJobTests.cs | 11 +- .../Jobs/AWS/SyncThingsJobTests.cs | 158 +---- .../Services/AWSDevicePropertyServiceTests.cs | 59 +- .../Services/AWSDeviceServiceTests.cs | 258 +++++++- .../Services/AWSExternalDeviceServiceTest.cs | 591 ------------------ .../Services/AwsExternalDeviceServiceTests.cs | 262 +++++++- .../Services/ExternalDeviceServiceTests.cs | 70 +++ 19 files changed, 885 insertions(+), 1082 deletions(-) delete mode 100644 src/IoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs delete mode 100644 src/IoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs delete mode 100644 src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSExternalDeviceServiceTest.cs diff --git a/src/IoTHub.Portal.Application/Mappers/DeviceModelProfile.cs b/src/IoTHub.Portal.Application/Mappers/DeviceModelProfile.cs index 990847638..453bfdd11 100644 --- a/src/IoTHub.Portal.Application/Mappers/DeviceModelProfile.cs +++ b/src/IoTHub.Portal.Application/Mappers/DeviceModelProfile.cs @@ -37,6 +37,13 @@ public DeviceModelProfile() .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ThingTypeId)) .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingTypeName)) .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.ThingTypeProperties.ThingTypeDescription ?? string.Empty)); + + _ = CreateMap() + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingTypeName)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ThingTypeId)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingTypeName)); } } } diff --git a/src/IoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs b/src/IoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs deleted file mode 100644 index 482c7175b..000000000 --- a/src/IoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace IoTHub.Portal.Application.Services.AWS -{ - using System.Threading.Tasks; - using Amazon.IoT.Model; - using Amazon.IotData.Model; - using IoTHub.Portal.Models.v10; - - public interface IAWSExternalDeviceService - { - Task GetDevice(string deviceName); - - Task CreateDevice(CreateThingRequest device); - - Task UpdateDevice(UpdateThingRequest device); - - Task DeleteDevice(DeleteThingRequest device); - - Task GetDeviceShadow(string deviceName); - - Task UpdateDeviceShadow(UpdateThingShadowRequest shadow); - Task GetEdgeDeviceNbDevices(IoTEdgeDevice device); - - Task IsEdgeThingType(DescribeThingTypeResponse thingType); - - Task> GetAllThings(); - - } -} diff --git a/src/IoTHub.Portal.Application/Services/IExternalDeviceService.cs b/src/IoTHub.Portal.Application/Services/IExternalDeviceService.cs index 424202dd4..52352e053 100644 --- a/src/IoTHub.Portal.Application/Services/IExternalDeviceService.cs +++ b/src/IoTHub.Portal.Application/Services/IExternalDeviceService.cs @@ -10,6 +10,7 @@ namespace IoTHub.Portal.Application.Services using Microsoft.Azure.Devices.Shared; using IoTHub.Portal.Domain.Shared; using Shared.Models.v10; + using Amazon.IoT.Model; public interface IExternalDeviceService { @@ -17,6 +18,8 @@ public interface IExternalDeviceService Task DeleteDeviceModel(ExternalDeviceModelDto deviceModel); + Task IsEdgeDeviceModel(ExternalDeviceModelDto deviceModel); + Task GetDevice(string deviceId); Task GetDeviceTwin(string deviceId); @@ -49,6 +52,8 @@ Task> GetAllDevice( Dictionary? searchTags = null, int pageSize = 10); + Task> GetAllThing(); + Task> GetAllEdgeDevice( string? continuationToken = null, string? searchText = null, diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs index 7f8f25e27..df0327c3b 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs @@ -8,10 +8,11 @@ namespace IoTHub.Portal.Infrastructure.Jobs.AWS using Amazon.IoT.Model; using AutoMapper; using IoTHub.Portal.Application.Managers; - using IoTHub.Portal.Application.Services.AWS; + using IoTHub.Portal.Application.Services; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Domain.Shared; using Microsoft.Extensions.Logging; using Quartz; @@ -25,7 +26,7 @@ public class SyncThingTypesJob : IJob private readonly IDeviceModelRepository deviceModelRepository; private readonly IAmazonIoT amazonIoTClient; private readonly IDeviceModelImageManager deviceModelImageManager; - private readonly IAWSExternalDeviceService awsExternalDeviceService; + private readonly IExternalDeviceService externalDeviceService; public SyncThingTypesJob( ILogger logger, @@ -34,14 +35,14 @@ public SyncThingTypesJob( IDeviceModelRepository deviceModelRepository, IAmazonIoT amazonIoTClient, IDeviceModelImageManager awsImageManager, - IAWSExternalDeviceService awsExternalDeviceService) + IExternalDeviceService externalDeviceService) { this.deviceModelImageManager = awsImageManager; this.mapper = mapper; this.unitOfWork = unitOfWork; this.deviceModelRepository = deviceModelRepository; this.amazonIoTClient = amazonIoTClient; - this.awsExternalDeviceService = awsExternalDeviceService; + this.externalDeviceService = externalDeviceService; this.logger = logger; } @@ -68,7 +69,7 @@ private async Task SyncThingTypesAsDeviceModels() foreach (var thingType in thingTypes) { - var isEdge = await awsExternalDeviceService.IsEdgeThingType(thingType); + var isEdge = await externalDeviceService.IsEdgeDeviceModel(this.mapper.Map(thingType)); // Cannot know if the thing type was created for an iotEdge or not, so skipping... if (!isEdge.HasValue) diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs index 7d5dfd35f..4f8b537d8 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs @@ -9,17 +9,17 @@ namespace IoTHub.Portal.Infrastructure.Jobs.AWS using Amazon.GreengrassV2.Model; using Amazon.GreengrassV2; using Amazon.IoT; - using Amazon.IoT.Model; using Amazon.IotData; using Amazon.IotData.Model; using AutoMapper; - using IoTHub.Portal.Application.Services.AWS; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Repositories; using Microsoft.Extensions.Logging; using Quartz; using Quartz.Util; + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Domain.Shared; [DisallowConcurrentExecution] public class SyncThingsJob : IJob @@ -36,7 +36,7 @@ public class SyncThingsJob : IJob private readonly IAmazonIoT amazonIoTClient; private readonly IAmazonIotData amazonIoTDataClient; private readonly IAmazonGreengrassV2 amazonGreenGrass; - private readonly IAWSExternalDeviceService awsExternalDeviceService; + private readonly IExternalDeviceService externalDeviceService; public SyncThingsJob( ILogger logger, @@ -50,7 +50,7 @@ public SyncThingsJob( IAmazonIoT amazonIoTClient, IAmazonIotData amazonIoTDataClient, IAmazonGreengrassV2 amazonGreenGrass, - IAWSExternalDeviceService awsExternalDeviceService) + IExternalDeviceService externalDeviceService) { this.mapper = mapper; this.unitOfWork = unitOfWork; @@ -63,7 +63,7 @@ public SyncThingsJob( this.amazonIoTDataClient = amazonIoTDataClient; this.amazonGreenGrass = amazonGreenGrass; this.logger = logger; - this.awsExternalDeviceService = awsExternalDeviceService; + this.externalDeviceService = externalDeviceService; } @@ -85,7 +85,7 @@ public async Task Execute(IJobExecutionContext context) private async Task SyncThingsAsDevices() { - var things = await this.awsExternalDeviceService.GetAllThings(); + var things = await this.externalDeviceService.GetAllThing(); foreach (var thing in things) { @@ -107,12 +107,7 @@ private async Task SyncThingsAsDevices() //Retrieve ThingType to know if it's an iotEdge try { - var thingType = await this.amazonIoTClient.DescribeThingTypeAsync(new DescribeThingTypeRequest() - { - ThingTypeName = thing.ThingTypeName - }); - - isEdge = await awsExternalDeviceService.IsEdgeThingType(thingType); + isEdge = await externalDeviceService.IsEdgeDeviceModel(this.mapper.Map(thing)); } catch (AmazonIoTException e) { diff --git a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs index e3762a259..9ca7611ba 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs @@ -6,7 +6,6 @@ namespace IoTHub.Portal.Infrastructure.Services.AWS using System.Collections.Generic; using System.Threading.Tasks; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Application.Services.AWS; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Models.v10; using Newtonsoft.Json; @@ -18,20 +17,25 @@ namespace IoTHub.Portal.Infrastructure.Services.AWS using IoTHub.Portal.Domain.Repositories; using Azure; using IoTHub.Portal.Application.Helpers; + using Amazon.IoT; + using Amazon.IotData; public class AWSDevicePropertyService : IDevicePropertyService { private readonly IDeviceModelPropertiesService deviceModelPropertiesService; - private readonly IAWSExternalDeviceService externalDeviceService; private readonly IDeviceRepository deviceRepository; + private readonly IAmazonIoT amazonIoTClient; + private readonly IAmazonIotData amazonIotDataClient; public AWSDevicePropertyService(IDeviceModelPropertiesService deviceModelPropertiesService - , IAWSExternalDeviceService externalDeviceService - , IDeviceRepository deviceRepository) + , IDeviceRepository deviceRepository + , IAmazonIoT amazonIoTClient + , IAmazonIotData amazonIotDataClient) { this.deviceModelPropertiesService = deviceModelPropertiesService; - this.externalDeviceService = externalDeviceService; this.deviceRepository = deviceRepository; + this.amazonIoTClient = amazonIoTClient; + this.amazonIotDataClient = amazonIotDataClient; } public async Task> GetProperties(string deviceId) @@ -42,7 +46,14 @@ public async Task> GetProperties(string deviceI throw new ResourceNotFoundException($"Unable to find the device {deviceId} in DB"); } - var deviceShadow = await this.externalDeviceService.GetDeviceShadow(deviceDb.Name); + var shadowResponse = await this.amazonIotDataClient.GetThingShadowAsync(new GetThingShadowRequest + { + ThingName = deviceDb.Name + }); + if (shadowResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to get the thing shadow with device name : {deviceDb.Name} due to an error in the Amazon IoT API : {shadowResponse.HttpStatusCode}"); + } IEnumerable items; try @@ -60,7 +71,7 @@ public async Task> GetProperties(string deviceI try { - desiredPropertiesAsJson = AWSDeviceHelper.RetrieveDesiredProperties(deviceShadow); + desiredPropertiesAsJson = AWSDeviceHelper.RetrieveDesiredProperties(shadowResponse); } catch (JsonReaderException e) { @@ -69,7 +80,7 @@ public async Task> GetProperties(string deviceI try { - reportedPropertiesAsJson = AWSDeviceHelper.RetrieveReportedProperties(deviceShadow); + reportedPropertiesAsJson = AWSDeviceHelper.RetrieveReportedProperties(shadowResponse); } catch (JsonReaderException e) { @@ -137,7 +148,12 @@ public async Task SetProperties(string deviceId, IEnumerable logger) : base(mapper, unitOfWork, deviceRepository, deviceTagValueRepository, labelRepository, externalDeviceService, deviceTagService, deviceModelImageManager, null!, portalDbContext, logger) { this.mapper = mapper; this.deviceRepository = deviceRepository; - this.externalDevicesService = externalDevicesService; + this.amazonIoTClient = amazonIoTClient; + this.amazonIotDataClient = amazonIotDataClient; } public override async Task CreateDevice(DeviceDetails device) { //Create Thing - var createThingRequest = this.mapper.Map(device); - var response = await this.externalDevicesService.CreateDevice(createThingRequest); - device.DeviceID = response.ThingId; + var thingResponse = await this.amazonIoTClient.CreateThingAsync(this.mapper.Map(device)); + if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to create the thing with device name : {device.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); + } + device.DeviceID = thingResponse.ThingId; //Create Thing Shadow - var shadowRequest = this.mapper.Map(device); - _ = await this.externalDevicesService.UpdateDeviceShadow(shadowRequest); + var shadowResponse = await this.amazonIotDataClient.UpdateThingShadowAsync(this.mapper.Map(device)); + if (shadowResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to create/update the thing shadow with device name : {device.DeviceName} due to an error in the Amazon IoT API : {shadowResponse.HttpStatusCode}"); + } //Create Thing in DB return await CreateDeviceInDatabase(device); @@ -58,8 +70,11 @@ public override async Task CreateDevice(DeviceDetails device) public override async Task UpdateDevice(DeviceDetails device) { //Update Thing - var updateThingRequest = this.mapper.Map(device); - _ = await this.externalDevicesService.UpdateDevice(updateThingRequest); + var thingResponse = await this.amazonIoTClient.UpdateThingAsync(this.mapper.Map(device)); + if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to update the thing with device name : {device.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); + } //Update Thing in DB return await UpdateDeviceInDatabase(device); @@ -67,10 +82,46 @@ public override async Task UpdateDevice(DeviceDetails device) public override async Task DeleteDevice(string deviceId) { - //Delete Thing + //Get device in DB var device = await deviceRepository.GetByIdAsync(deviceId); - var deleteThingRequest = this.mapper.Map(device); - _ = await this.externalDevicesService.DeleteDevice(deleteThingRequest); + + if (device == null) + { + throw new ResourceNotFoundException($"The device with id {deviceId} doesn't exist"); + } + + //Retrieve all thing principals and detach it before deleting the thing + var principals = await this.amazonIoTClient.ListThingPrincipalsAsync(new ListThingPrincipalsRequest + { + NextToken = string.Empty, + ThingName = device.Name + }); + + if (principals.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to retreive Thing {device.Name} principals due to an error in the Amazon IoT API : {principals.HttpStatusCode}"); + } + + foreach (var principal in principals.Principals) + { + var detachPrincipal = await this.amazonIoTClient.DetachThingPrincipalAsync(new DetachThingPrincipalRequest + { + Principal = principal, + ThingName = device.Name + }); + + if (detachPrincipal.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to detach Thing {device.Name} principal due to an error in the Amazon IoT API : {detachPrincipal.HttpStatusCode}"); + } + } + + //Delete the thing type after detaching the principal + var deleteResponse = await this.amazonIoTClient.DeleteThingAsync(this.mapper.Map(device)); + if (deleteResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to delete the thing with device name : {device.Name} due to an error in the Amazon IoT API : {deleteResponse.HttpStatusCode}"); + } //Delete Thing in DB await DeleteDeviceInDatabase(deviceId); diff --git a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs index 0d083caea..b7300763d 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs @@ -6,12 +6,13 @@ namespace IoTHub.Portal.Infrastructure.Services using System; using System.Threading.Tasks; using Amazon.GreengrassV2; + using Amazon.IoT; using Amazon.IoT.Model; using AutoMapper; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Application.Services.AWS; using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Domain.Repositories; using IoTHub.Portal.Infrastructure.Helpers; using IoTHub.Portal.Models.v10; @@ -21,7 +22,6 @@ public class AWSEdgeDevicesService : EdgeDevicesServiceBase, IEdgeDevicesService /// /// The device idevice service. /// - private readonly IAWSExternalDeviceService awsExternalDevicesService; private readonly IExternalDeviceService externalDeviceService; private readonly IUnitOfWork unitOfWork; private readonly IEdgeDeviceRepository edgeDeviceRepository; @@ -30,12 +30,13 @@ public class AWSEdgeDevicesService : EdgeDevicesServiceBase, IEdgeDevicesService private readonly IConfigService configService; private readonly ConfigHandler configHandler; private readonly IMapper mapper; + private readonly IAmazonIoT amazonIoTClient; + private readonly IAmazonGreengrassV2 amazonGreengrass; public AWSEdgeDevicesService( ConfigHandler configHandler, IEdgeEnrollementHelper edgeEnrollementHelper, IExternalDeviceService externalDeviceService, - IAWSExternalDeviceService awsExternalDevicesService, IDeviceTagService deviceTagService, IConfigService configService, IMapper mapper, @@ -44,12 +45,13 @@ public AWSEdgeDevicesService( IEdgeDeviceModelRepository deviceModelRepository, IDeviceTagValueRepository deviceTagValueRepository, ILabelRepository labelRepository, - IDeviceModelImageManager deviceModelImageManager) + IDeviceModelImageManager deviceModelImageManager, + IAmazonIoT amazonIoTClient, + IAmazonGreengrassV2 amazonGreengrass) : base(deviceTagService, edgeDeviceRepository, mapper, deviceModelImageManager, deviceTagValueRepository, labelRepository) { this.configHandler = configHandler; this.edgeEnrollementHelper = edgeEnrollementHelper; - this.awsExternalDevicesService = awsExternalDevicesService; this.externalDeviceService = externalDeviceService; this.configService = configService; this.deviceModelRepository = deviceModelRepository; @@ -58,6 +60,9 @@ public AWSEdgeDevicesService( this.edgeDeviceRepository = edgeDeviceRepository; this.mapper = mapper; + + this.amazonIoTClient = amazonIoTClient; + this.amazonGreengrass = amazonGreengrass; } /// @@ -69,19 +74,24 @@ public async Task CreateEdgeDevice(IoTEdgeDevice edgeDevice) { ArgumentNullException.ThrowIfNull(edgeDevice, nameof(edgeDevice)); + //Retrieve Device Model var model = await this.deviceModelRepository.GetByIdAsync(edgeDevice.ModelId); - if (model == null) { throw new InvalidOperationException($"Edge model '{edgeDevice.ModelId}' doesn't exist!"); } + //Create Thing var createThingRequest = this.mapper.Map(edgeDevice); createThingRequest.ThingTypeName = model.Name; + var thingResponse = await this.amazonIoTClient.CreateThingAsync(createThingRequest); + if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to create the thing with device name : {edgeDevice.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); + } + edgeDevice.DeviceId = thingResponse.ThingId; - var response = await this.awsExternalDevicesService.CreateDevice(createThingRequest); - edgeDevice.DeviceId = response.ThingId; - + //Create EdgeDevice in DB var result = await base.CreateEdgeDeviceInDatabase(edgeDevice); await this.unitOfWork.SaveAsync(); @@ -98,11 +108,15 @@ public async Task UpdateEdgeDevice(IoTEdgeDevice edgeDevice) { ArgumentNullException.ThrowIfNull(edgeDevice, nameof(edgeDevice)); - var updateThingRequest = this.mapper.Map(edgeDevice); - _ = await this.awsExternalDevicesService.UpdateDevice(updateThingRequest); + //Update Thing + var thingResponse = await this.amazonIoTClient.UpdateThingAsync(this.mapper.Map(edgeDevice)); + if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new InternalServerErrorException($"Unable to update the thing with device name : {edgeDevice.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); + } + //Update EdgeDevice in DB var result = await UpdateEdgeDeviceInDatabase(edgeDevice); - await this.unitOfWork.SaveAsync(); return result; @@ -138,7 +152,7 @@ public async Task GetEdgeDevice(string edgeDeviceId) var model = await this.deviceModelRepository.GetByIdAsync(deviceDto.ModelId); deviceDto.Modules = await this.configService.GetConfigModuleList(model.ExternalIdentifier!); - deviceDto.NbDevices = await this.awsExternalDevicesService.GetEdgeDeviceNbDevices(deviceDto); + deviceDto.NbDevices = await this.GetEdgeDeviceNbDevices(deviceDto); deviceDto.NbModules = deviceDto.Modules.Count; deviceDto.ConnectionState = deviceDto.RuntimeResponse == CoreDeviceStatus.HEALTHY ? "Connected" : "Disconnected"; @@ -178,5 +192,17 @@ public async Task GetEdgeDeviceEnrollementScript(string deviceId, string return await this.externalDeviceService.CreateEnrollementScript(template, device); } + private async Task GetEdgeDeviceNbDevices(IoTEdgeDevice device) + { + var listClientDevices = await this.amazonGreengrass.ListClientDevicesAssociatedWithCoreDeviceAsync( + new Amazon.GreengrassV2.Model.ListClientDevicesAssociatedWithCoreDeviceRequest + { + CoreDeviceThingName = device.DeviceName + }); + return listClientDevices.HttpStatusCode != System.Net.HttpStatusCode.OK + ? throw new InternalServerErrorException($"Can not list Client Devices Associated to {device.DeviceName} Core Device due to an error in the Amazon IoT API.") + : listClientDevices.AssociatedClientDevices.Count; + } + } } diff --git a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs deleted file mode 100644 index 5b74e504f..000000000 --- a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace IoTHub.Portal.Infrastructure.Services.AWS -{ - using System.Threading.Tasks; - using Amazon.GreengrassV2; - using Amazon.IoT; - using Amazon.IoT.Model; - using Amazon.IotData; - using Amazon.IotData.Model; - using IoTHub.Portal.Application.Services.AWS; - using IoTHub.Portal.Domain.Exceptions; - using IoTHub.Portal.Models.v10; - using Microsoft.Extensions.Logging; - - public class AWSExternalDeviceService : IAWSExternalDeviceService - { - private readonly IAmazonIoT amazonIotClient; - private readonly IAmazonIotData amazonIotDataClient; - private readonly IAmazonGreengrassV2 amazonGreenGrasss; - private readonly ILogger logger; - - public AWSExternalDeviceService( - IAmazonIoT amazonIoTClient, - IAmazonIotData amazonIotDataClient, - IAmazonGreengrassV2 amazonGreenGrasss, - ILogger logger) - { - this.amazonIotClient = amazonIoTClient; - this.amazonIotDataClient = amazonIotDataClient; - this.amazonGreenGrasss = amazonGreenGrasss; - this.logger = logger; - } - - public async Task GetDevice(string deviceName) - { - var deviceRequest = new DescribeThingRequest { ThingName = deviceName }; - - var deviceResponse = await this.amazonIotClient.DescribeThingAsync(deviceRequest); - - if (deviceResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to get the thing with device name : {deviceName} due to an error in the Amazon IoT API : {deviceResponse.HttpStatusCode}"); - } - - return deviceResponse; - } - - public async Task CreateDevice(CreateThingRequest device) - { - var thingResponse = await this.amazonIotClient.CreateThingAsync(device); - - if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to create the thing with device name : {device.ThingName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); - } - - return thingResponse; - } - - public async Task UpdateDevice(UpdateThingRequest device) - { - var thingResponse = await this.amazonIotClient.UpdateThingAsync(device); - - if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to update the thing with device name : {device.ThingName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); - } - - return thingResponse; - } - - public async Task DeleteDevice(DeleteThingRequest device) - { - //Retreive all thing princpals and detach it before deleting the thing - var principals = await this.amazonIotClient.ListThingPrincipalsAsync(new ListThingPrincipalsRequest - { - NextToken = string.Empty, - ThingName = device.ThingName - }); - - if (principals.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to retreive Thing {device.ThingName} principals due to an error in the Amazon IoT API : {principals.HttpStatusCode}"); - - } - - foreach (var principal in principals.Principals) - { - var detachPrincipal = await this.amazonIotClient.DetachThingPrincipalAsync(new DetachThingPrincipalRequest - { - Principal = principal, - ThingName = device.ThingName - }); - - if (detachPrincipal.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to detach Thing {device.ThingName} principal due to an error in the Amazon IoT API : {detachPrincipal.HttpStatusCode}"); - - } - } - - //Delete the thing type before detaching the princiapl - var deleteResponse = await this.amazonIotClient.DeleteThingAsync(device); - - if (deleteResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to delete the thing with device name : {device.ThingName} due to an error in the Amazon IoT API : {deleteResponse.HttpStatusCode}"); - } - - return deleteResponse; - } - - public async Task GetDeviceShadow(string deviceName) - { - var shadowRequest = new GetThingShadowRequest { ThingName = deviceName }; - - var shadowResponse = await this.amazonIotDataClient.GetThingShadowAsync(shadowRequest); - - if (shadowResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to get the thing shadow with device name : {deviceName} due to an error in the Amazon IoT API : {shadowResponse.HttpStatusCode}"); - } - - return shadowResponse; - } - - public async Task UpdateDeviceShadow(UpdateThingShadowRequest shadow) - { - var shadowResponse = await this.amazonIotDataClient.UpdateThingShadowAsync(shadow); - - if (shadowResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to create/update the thing shadow with device name : {shadow.ThingName} due to an error in the Amazon IoT API : {shadowResponse.HttpStatusCode}"); - } - - return shadowResponse; - } - - public async Task GetEdgeDeviceNbDevices(IoTEdgeDevice device) - { - var listClientDevices = await this.amazonGreenGrasss.ListClientDevicesAssociatedWithCoreDeviceAsync( - new Amazon.GreengrassV2.Model.ListClientDevicesAssociatedWithCoreDeviceRequest - { - CoreDeviceThingName = device.DeviceName - }); - return listClientDevices.HttpStatusCode != System.Net.HttpStatusCode.OK - ? throw new InternalServerErrorException($"Can not list Client Devices Associated to {device.DeviceName} Core Device due to an error in the Amazon IoT API.") - : listClientDevices.AssociatedClientDevices.Count; - } - - public async Task IsEdgeThingType(DescribeThingTypeResponse thingType) - { - var response = await this.amazonIotClient.ListTagsForResourceAsync(new ListTagsForResourceRequest - { - ResourceArn = thingType.ThingTypeArn - }); - - do - { - if (response == null || !response.Tags.Any()) - { - return null; - } - - var iotEdgeTag = response.Tags.Where(c => c.Key.Equals("iotEdge", StringComparison.OrdinalIgnoreCase)); - - if (!iotEdgeTag.Any()) - { - response = await this.amazonIotClient.ListTagsForResourceAsync(new ListTagsForResourceRequest - { - ResourceArn = thingType.ThingTypeArn, - NextToken = response.NextToken - }); - - continue; - } - - return bool.TryParse(iotEdgeTag.Single().Value, out var result) ? result : null; - - } while (true); - } - - public async Task> GetAllThings() - { - var things = new List(); - - var marker = string.Empty; - - do - { - var request = new ListThingsRequest - { - Marker = marker - }; - - var response = await this.amazonIotClient.ListThingsAsync(request); - - foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName })) - { - try - { - things.Add(await this.amazonIotClient.DescribeThingAsync(requestDescribeThing)); - } - catch (AmazonIoTException e) - { - this.logger.LogWarning($"Cannot import device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); - - continue; - } - } - - marker = response.NextMarker; - } - while (!string.IsNullOrEmpty(marker)); - - return things; - } - } -} diff --git a/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs b/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs index f0886da12..676e39521 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs @@ -17,7 +17,6 @@ namespace IoTHub.Portal.Infrastructure.Services using AutoMapper; using IoTHub.Portal; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Application.Services.AWS; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Shared; using IoTHub.Portal.Models.v10; @@ -25,6 +24,7 @@ namespace IoTHub.Portal.Infrastructure.Services using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; using Shared.Models.v10; + using ListTagsForResourceRequest = Amazon.IoT.Model.ListTagsForResourceRequest; using ResourceAlreadyExistsException = Amazon.IoT.Model.ResourceAlreadyExistsException; using ResourceNotFoundException = Amazon.IoT.Model.ResourceNotFoundException; using Tag = Amazon.IoT.Model.Tag; @@ -41,7 +41,6 @@ public class AwsExternalDeviceService : IExternalDeviceService private readonly IAmazonGreengrassV2 greengrass; private readonly IAmazonSecretsManager amazonSecretsManager; private readonly IAmazonIotData amazonIotData; - private readonly IAWSExternalDeviceService awsExternalDeviceService; private readonly ILogger logger; @@ -52,7 +51,6 @@ public AwsExternalDeviceService( IAmazonIoT amazonIoTClient, IAmazonGreengrassV2 greengrass, IAmazonSecretsManager amazonSecretsManager, - IAWSExternalDeviceService awsExternalDeviceService, ILogger logger, IAmazonIotData amazonIotData) { @@ -61,7 +59,6 @@ public AwsExternalDeviceService( this.amazonIoTClient = amazonIoTClient; this.greengrass = greengrass; this.amazonSecretsManager = amazonSecretsManager; - this.awsExternalDeviceService = awsExternalDeviceService; this.logger = logger; this.amazonIotData = amazonIotData; } @@ -140,6 +137,43 @@ public async Task DeleteDeviceModel(ExternalDeviceModelDto deviceModel) } } + public async Task IsEdgeDeviceModel(ExternalDeviceModelDto deviceModel) + { + var thingType = await this.amazonIoTClient.DescribeThingTypeAsync(new DescribeThingTypeRequest() + { + ThingTypeName = deviceModel.Name + }); + + var response = await this.amazonIoTClient.ListTagsForResourceAsync(new ListTagsForResourceRequest + { + ResourceArn = thingType.ThingTypeArn + }); + + do + { + if (response == null || !response.Tags.Any()) + { + return null; + } + + var iotEdgeTag = response.Tags.Where(c => c.Key.Equals("iotEdge", StringComparison.OrdinalIgnoreCase)); + + if (!iotEdgeTag.Any()) + { + response = await this.amazonIoTClient.ListTagsForResourceAsync(new ListTagsForResourceRequest + { + ResourceArn = thingType.ThingTypeArn, + NextToken = response.NextToken + }); + + continue; + } + + return bool.TryParse(iotEdgeTag.Single().Value, out var result) ? result : null; + + } while (true); + } + public Task ExecuteC2DMethod(string deviceId, CloudToDeviceMethod method) { throw new NotImplementedException(); @@ -155,6 +189,42 @@ public Task> GetAllDevice(string? continuationToken = nul throw new NotImplementedException(); } + public async Task> GetAllThing() + { + var things = new List(); + + var marker = string.Empty; + + do + { + var request = new ListThingsRequest + { + Marker = marker + }; + + var response = await this.amazonIoTClient.ListThingsAsync(request); + + foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName })) + { + try + { + things.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); + } + catch (AmazonIoTException e) + { + this.logger.LogWarning($"Cannot import device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); + + continue; + } + } + + marker = response.NextMarker; + } + while (!string.IsNullOrEmpty(marker)); + + return things; + } + public Task> GetAllEdgeDevice(string? continuationToken = null, string? searchText = null, bool? searchStatus = null, string? searchType = null, int pageSize = 10) { throw new NotImplementedException(); @@ -209,7 +279,7 @@ private async Task> Count() var deviceCount = 0; var edgeDeviceCount = 0; - var devices = await this.awsExternalDeviceService.GetAllThings(); + var devices = await this.GetAllThing(); var dico = new Dictionary(); bool? isEdge; @@ -218,12 +288,7 @@ private async Task> Count() { try { - var thingType = await this.amazonIoTClient.DescribeThingTypeAsync(new DescribeThingTypeRequest() - { - ThingTypeName = device.ThingTypeName - }); - - isEdge = await awsExternalDeviceService.IsEdgeThingType(thingType); + isEdge = await this.IsEdgeDeviceModel(this.mapper.Map(device)); } catch (AmazonIoTException e) { diff --git a/src/IoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs b/src/IoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs index 546a7ed77..27f8bc9f8 100644 --- a/src/IoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs +++ b/src/IoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs @@ -11,7 +11,6 @@ namespace IoTHub.Portal.Infrastructure.Startup using Amazon.SecretsManager; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Application.Services.AWS; using IoTHub.Portal.Domain; using IoTHub.Portal.Infrastructure.Jobs; using IoTHub.Portal.Infrastructure.Jobs.AWS; @@ -61,7 +60,6 @@ private static IServiceCollection ConfigureAWSServices(this IServiceCollection s .AddTransient() .AddTransient(typeof(IDeviceModelService<,>), typeof(AwsDeviceModelService<,>)) .AddTransient, AWSDeviceService>() - .AddTransient() .AddTransient() .AddTransient() .AddTransient() diff --git a/src/IoTHub.Portal.Server/Services/ExternalDeviceService.cs b/src/IoTHub.Portal.Server/Services/ExternalDeviceService.cs index 012552997..6fc440637 100644 --- a/src/IoTHub.Portal.Server/Services/ExternalDeviceService.cs +++ b/src/IoTHub.Portal.Server/Services/ExternalDeviceService.cs @@ -734,5 +734,15 @@ public Task RemoveDeviceCredentials(IoTEdgeDevice device) { throw new NotImplementedException(); } + + public Task IsEdgeDeviceModel(ExternalDeviceModelDto deviceModel) + { + throw new NotImplementedException(); + } + + public Task> GetAllThing() + { + throw new NotImplementedException(); + } } } diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs index f6a8348fd..fc190008b 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs @@ -12,10 +12,11 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS using Amazon.IoT.Model; using AutoFixture; using IoTHub.Portal.Application.Managers; - using IoTHub.Portal.Application.Services.AWS; + using IoTHub.Portal.Application.Services; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Domain.Shared; using IoTHub.Portal.Infrastructure.Jobs.AWS; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; using Microsoft.Extensions.DependencyInjection; @@ -29,7 +30,7 @@ public class SyncThingTypesJobTests : BackendUnitTest private Mock iaAmazon; private Mock mockAWSImageManager; - private Mock mockAWSExternalDeviceService; + private Mock mockExternalDeviceService; private Mock mockUnitOfWork; private Mock mockDeviceModelRepository; @@ -38,13 +39,13 @@ public override void Setup() base.Setup(); this.mockAWSImageManager = MockRepository.Create(); - this.mockAWSExternalDeviceService = MockRepository.Create(); + this.mockExternalDeviceService = MockRepository.Create(); this.mockUnitOfWork = MockRepository.Create(); this.mockDeviceModelRepository = MockRepository.Create(); this.iaAmazon = MockRepository.Create(); _ = ServiceCollection.AddSingleton(this.mockAWSImageManager.Object); - _ = ServiceCollection.AddSingleton(this.mockAWSExternalDeviceService.Object); + _ = ServiceCollection.AddSingleton(this.mockExternalDeviceService.Object); _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceModelRepository.Object); _ = ServiceCollection.AddSingleton(this.iaAmazon.Object); @@ -124,7 +125,7 @@ public async Task Execute_SyncNewAndExistingAndDepprecatedThingTypes_DeviceModel _ = this.iaAmazon.Setup(client => client.DescribeThingTypeAsync(It.Is(c => c.ThingTypeName == depcrecatedThingType.ThingTypeName), It.IsAny())) .ReturnsAsync(depcrecatedThingType); - _ = this.mockAWSExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.mockExternalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); var existingDeviceModel = new DeviceModel diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs index 1e482d494..cd85bd200 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs @@ -16,10 +16,11 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS using Amazon.IotData; using Amazon.IotData.Model; using AutoFixture; - using IoTHub.Portal.Application.Services.AWS; + using IoTHub.Portal.Application.Services; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Domain.Shared; using IoTHub.Portal.Infrastructure.Jobs.AWS; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; using Microsoft.Extensions.DependencyInjection; @@ -40,7 +41,7 @@ public class SyncThingsJobTests : BackendUnitTest private Mock mockDeviceModelRepository; private Mock mockEdgeDeviceModelRepository; private Mock mockDeviceTagValueRepository; - private Mock awsExternalDeviceService; + private Mock externalDeviceService; public override void Setup() { @@ -55,7 +56,7 @@ public override void Setup() this.amazonIoTClient = MockRepository.Create(); this.amazonIoTDataClient = MockRepository.Create(); this.amazonGreenGrass = MockRepository.Create(); - this.awsExternalDeviceService = MockRepository.Create(); + this.externalDeviceService = MockRepository.Create(); _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceRepository.Object); @@ -66,7 +67,7 @@ public override void Setup() _ = ServiceCollection.AddSingleton(this.amazonIoTClient.Object); _ = ServiceCollection.AddSingleton(this.amazonIoTDataClient.Object); _ = ServiceCollection.AddSingleton(this.amazonGreenGrass.Object); - _ = ServiceCollection.AddSingleton(this.awsExternalDeviceService.Object); + _ = ServiceCollection.AddSingleton(this.externalDeviceService.Object); _ = ServiceCollection.AddSingleton(); @@ -103,18 +104,10 @@ public async Task ExecuteNewDeviceDeviceCreated() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); _ = this.mockDeviceModelRepository @@ -200,19 +193,10 @@ public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); _ = this.mockDeviceModelRepository @@ -283,18 +267,10 @@ public async Task ExecuteExistingDeviceWithOlderVersionDeviceNotUpdated() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); _ = this.mockDeviceModelRepository @@ -362,18 +338,10 @@ public async Task ExecuteNewDeviceWithUnknownIsEdgeTagSkipped() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync((bool?)null); _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) @@ -430,18 +398,10 @@ public async Task ExecuteNewDeviceWithUnknownDeviceModelSkipped() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); _ = this.mockDeviceModelRepository @@ -503,18 +463,10 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped(HttpStatusCode thing } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); _ = this.mockDeviceModelRepository @@ -578,7 +530,7 @@ public async Task ExecuteNewDeviceWithAmazonIotExceptionSkipped() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); @@ -636,19 +588,10 @@ public async Task ExecuteNewDeviceWithAmazonIotDataExceptionSkipped() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); _ = this.mockDeviceModelRepository @@ -713,19 +656,10 @@ public async Task ExecuteNewEdgeDeviceEdgeDeviceCreated() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(true); _ = this.mockEdgeDeviceModelRepository @@ -810,18 +744,10 @@ public async Task ExecuteExistingEdgeDeviceWithHigherVersionEdgeDeviceUpdated() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(true); _ = this.mockEdgeDeviceModelRepository @@ -893,18 +819,10 @@ public async Task ExecuteExistingEdgeDeviceWithOlderVersionEdgeDeviceNotUpdated( } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(true); _ = this.mockEdgeDeviceModelRepository @@ -972,18 +890,10 @@ public async Task ExecuteNewEdgeDeviceWithUnknownEdgeDeviceModelSkipped() } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(true); _ = this.mockEdgeDeviceModelRepository @@ -1045,18 +955,10 @@ public async Task ExecuteNewEdgeDeviceWithoutCoreDeviceDeviceCreatedWithDisconne } }; - _ = this.awsExternalDeviceService.Setup(client => client.GetAllThings()) + _ = this.externalDeviceService.Setup(client => client.GetAllThing()) .ReturnsAsync(listDescribeThingResponse); - _ = this.amazonIoTClient.Setup(client => client.DescribeThingTypeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DescribeThingTypeResponse() - { - ThingTypeId = expectedDeviceModel.Id, - ThingTypeName = expectedDeviceModel.Name, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.awsExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + _ = this.externalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(true); _ = this.mockEdgeDeviceModelRepository diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs index f67dfd829..ee5aac341 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs @@ -17,7 +17,6 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services using AutoMapper; using Azure; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Application.Services.AWS; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Exceptions; @@ -67,7 +66,6 @@ public void SetUp() _ = ServiceCollection.AddSingleton(this.mockConfiguration.Object); _ = ServiceCollection.AddSingleton(DbContext); _ = ServiceCollection.AddSingleton(this.mockGreenGrass.Object); - _ = ServiceCollection.AddSingleton(); _ = ServiceCollection.AddSingleton(); Services = ServiceCollection.BuildServiceProvider(); @@ -92,6 +90,33 @@ public async Task WhenDeviceIdNotExistGetPropertiesShouldThrowResourceNotFoundEx MockRepository.VerifyAll(); } + [Test] + public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenHttpStatusCodeIsNotOKForGetThingShadow() + { + // Arrange + var device = new Device() + { + Id = "aaa", + DeviceModelId = "bbb" + }; + + _ = this.mockDeviceRepository.Setup(c => c.GetByIdAsync("aaa")) + .ReturnsAsync(device); + + _ = this.mockAmazonIotDataClient.Setup(iotDataClient => iotDataClient.GetThingShadowAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetThingShadowResponse + { + HttpStatusCode = HttpStatusCode.BadRequest + }); + + // Act + var act = () => this.awsDevicePropertyService.GetProperties(device.Id); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + [Test] public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursOnGettingProperties() { @@ -229,5 +254,35 @@ public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueO _ = await act.Should().ThrowAsync(); MockRepository.VerifyAll(); } + + [Test] + public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenHttpStatusIsNotOKForUpdateThingShadow() + { + // Arrange + var device = new Device() + { + Id = "aaa", + DeviceModelId = "bbb" + }; + + _ = this.mockDeviceRepository.Setup(c => c.GetByIdAsync(device.Id)) + .ReturnsAsync(device); + + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(device.DeviceModelId)) + .ReturnsAsync(Enumerable.Empty()); + + _ = this.mockAmazonIotDataClient.Setup(iotDataClient => iotDataClient.UpdateThingShadowAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UpdateThingShadowResponse + { + HttpStatusCode = HttpStatusCode.BadRequest + }); + + // Act + var act = () => this.awsDevicePropertyService.SetProperties(device.Id, null); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } } } diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs index 8d56df00d..f6282b98d 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs @@ -15,7 +15,6 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services using AutoMapper; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Application.Services.AWS; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Repositories; @@ -31,6 +30,8 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services using NUnit.Framework; using Device = Portal.Domain.Entities.Device; using ResourceNotFoundException = Portal.Domain.Exceptions.ResourceNotFoundException; + using System.Threading; + using IoTHub.Portal.Domain.Exceptions; [TestFixture] public class AWSDeviceServiceTests : BackendUnitTest @@ -44,7 +45,6 @@ public class AWSDeviceServiceTests : BackendUnitTest private Mock mockAmazonIotClient; private Mock mockAmazonIotDataClient; private Mock mockConfiguration; - private Mock mockAWSExternalDevicesService; private Mock mockExternalDeviceService; private IDeviceService awsDeviceService; @@ -63,7 +63,6 @@ public void SetUp() this.mockAmazonIotClient = MockRepository.Create(); this.mockAmazonIotDataClient = MockRepository.Create(); this.mockConfiguration = MockRepository.Create(); - this.mockAWSExternalDevicesService = MockRepository.Create(); this.mockExternalDeviceService = MockRepository.Create(); _ = ServiceCollection.AddSingleton(this.mockDeviceRepository.Object); @@ -75,7 +74,6 @@ public void SetUp() _ = ServiceCollection.AddSingleton(this.mockAmazonIotClient.Object); _ = ServiceCollection.AddSingleton(this.mockAmazonIotDataClient.Object); _ = ServiceCollection.AddSingleton(this.mockConfiguration.Object); - _ = ServiceCollection.AddSingleton(this.mockAWSExternalDevicesService.Object); _ = ServiceCollection.AddSingleton(this.mockExternalDeviceService.Object); _ = ServiceCollection.AddSingleton(DbContext); _ = ServiceCollection.AddSingleton, AWSDeviceService>(); @@ -96,14 +94,14 @@ public async Task CreateADeviceShouldReturnAValue() DeviceID = Fixture.Create() }; - _ = this.mockAWSExternalDevicesService.Setup(service => service.CreateDevice(It.IsAny())) + _ = this.mockAmazonIotClient.Setup(service => service.CreateThingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new CreateThingResponse() { ThingId = deviceDto.DeviceID, HttpStatusCode = HttpStatusCode.OK }); - _ = this.mockAWSExternalDevicesService.Setup(service => service.UpdateDeviceShadow(It.IsAny())) + _ = this.mockAmazonIotDataClient.Setup(service => service.UpdateThingShadowAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new UpdateThingShadowResponse() { HttpStatusCode = HttpStatusCode.OK @@ -123,6 +121,59 @@ public async Task CreateADeviceShouldReturnAValue() MockRepository.VerifyAll(); } + [Test] + public async Task CreateDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNotOKForCreateThing() + { + // Arrange + var deviceDto = new DeviceDetails() + { + DeviceID = Fixture.Create() + }; + + _ = this.mockAmazonIotClient.Setup(service => service.CreateThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new CreateThingResponse() + { + HttpStatusCode = HttpStatusCode.BadRequest + }); + + //Act + var result = () => this.awsDeviceService.CreateDevice(deviceDto); + + //Assert + _ = await result.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task CreateDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNotOKForUpdateThingShadow() + { + // Arrange + var deviceDto = new DeviceDetails() + { + DeviceID = Fixture.Create() + }; + + _ = this.mockAmazonIotClient.Setup(service => service.CreateThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new CreateThingResponse() + { + ThingId = deviceDto.DeviceID, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockAmazonIotDataClient.Setup(service => service.UpdateThingShadowAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UpdateThingShadowResponse() + { + HttpStatusCode = HttpStatusCode.BadRequest + }); + + //Act + var result = () => this.awsDeviceService.CreateDevice(deviceDto); + + //Assert + _ = await result.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + [Test] public async Task CreateDeviceDuplicateExceptionIsThrown() { @@ -132,14 +183,14 @@ public async Task CreateDeviceDuplicateExceptionIsThrown() DeviceID = Fixture.Create() }; - _ = this.mockAWSExternalDevicesService.Setup(service => service.CreateDevice(It.IsAny())) + _ = this.mockAmazonIotClient.Setup(service => service.CreateThingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new CreateThingResponse() { ThingId = deviceDto.DeviceID, HttpStatusCode = HttpStatusCode.OK }); - _ = this.mockAWSExternalDevicesService.Setup(service => service.UpdateDeviceShadow(It.IsAny())) + _ = this.mockAmazonIotDataClient.Setup(service => service.UpdateThingShadowAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new UpdateThingShadowResponse() { HttpStatusCode = HttpStatusCode.OK @@ -169,7 +220,7 @@ public async Task UpdateDeviceShouldReturnValue() DeviceName = Fixture.Create(), }; - _ = this.mockAWSExternalDevicesService.Setup(service => service.UpdateDevice(It.IsAny())) + _ = this.mockAmazonIotClient.Setup(service => service.UpdateThingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new UpdateThingResponse() { HttpStatusCode = HttpStatusCode.OK @@ -208,6 +259,30 @@ public async Task UpdateDeviceShouldReturnValue() MockRepository.VerifyAll(); } + [Test] + public async Task UpdateDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNotOKForUpdateThing() + { + // Arrange + var deviceDto = new DeviceDetails + { + DeviceID = Fixture.Create(), + DeviceName = Fixture.Create(), + }; + + _ = this.mockAmazonIotClient.Setup(service => service.UpdateThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UpdateThingResponse() + { + HttpStatusCode = HttpStatusCode.BadRequest + }); + + // Act + var result = () => this.awsDeviceService.UpdateDevice(deviceDto); + + // Assert + _ = await result.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + [Test] public async Task UpdateDeviceThatNotExistThrowResourceNotFoundException() { @@ -218,7 +293,7 @@ public async Task UpdateDeviceThatNotExistThrowResourceNotFoundException() DeviceName = Fixture.Create(), }; - _ = this.mockAWSExternalDevicesService.Setup(service => service.UpdateDevice(It.IsAny())) + _ = this.mockAmazonIotClient.Setup(service => service.UpdateThingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new UpdateThingResponse() { HttpStatusCode = HttpStatusCode.OK @@ -245,7 +320,7 @@ public async Task UpdateDeviceWhenDbUpdateExceptionIsRaisedCannotInsertNullExcep DeviceName = Fixture.Create(), }; - _ = this.mockAWSExternalDevicesService.Setup(service => service.UpdateDevice(It.IsAny())) + _ = this.mockAmazonIotClient.Setup(service => service.UpdateThingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new UpdateThingResponse() { HttpStatusCode = HttpStatusCode.OK @@ -288,7 +363,23 @@ public async Task DeleteDevice() Labels = Fixture.CreateMany