diff --git a/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs b/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs index d3fb5e818..0338efc28 100644 --- a/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs @@ -23,6 +23,7 @@ public interface IAWSExternalDeviceService Task UpdateDeviceShadow(UpdateThingShadowRequest shadow); Task GetEdgeDeviceNbDevices(IoTEdgeDevice device); + Task IsEdgeThingType(DescribeThingTypeResponse thingType); } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs index c591877fe..74b4ba886 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs @@ -81,35 +81,42 @@ private async Task> GetAllGreenGrassDeployments() var nextToken = string.Empty; - var getAllAwsGreenGrassDeployments = await this.amazonGreenGrass.ListDeploymentsAsync( - new ListDeploymentsRequest + do + { + var request = new ListDeploymentsRequest { NextToken = nextToken, HistoryFilter = DeploymentHistoryFilter.LATEST_ONLY - }); + }; - foreach (var deployment in getAllAwsGreenGrassDeployments.Deployments) - { - var awsThingGroupRegex = new Regex(@"/([^/]+)$"); - var matches = awsThingGroupRegex.Match(deployment.TargetArn); + var response = await this.amazonGreenGrass.ListDeploymentsAsync(request); - if (matches.Success && matches.Groups.Count > 1) + foreach (var deployment in response.Deployments) { - var thinggroupName = matches.Groups[1].Value; - var s = await this.amazonIoTClient.DescribeThingGroupAsync(new Amazon.IoT.Model.DescribeThingGroupRequest { ThingGroupName = thinggroupName }); - if (s.QueryString != null) + var awsThingGroupRegex = new Regex(@"/([^/]+)$"); + var matches = awsThingGroupRegex.Match(deployment.TargetArn); + + if (matches.Success && matches.Groups.Count > 1) { - var iotEdgeModel = new IoTEdgeModel + var thinggroupName = matches.Groups[1].Value; + var s = await this.amazonIoTClient.DescribeThingGroupAsync(new Amazon.IoT.Model.DescribeThingGroupRequest { ThingGroupName = thinggroupName }); + if (s.QueryString != null) { - ModelId = deployment.DeploymentId, //Instead of giving a random Id here, we can give the deploymentID - Name = deployment.DeploymentName, - ExternalIdentifier = deployment.DeploymentId - }; - deployments.Add(iotEdgeModel); + var iotEdgeModel = new IoTEdgeModel + { + ModelId = deployment.DeploymentId, //Instead of giving a random Id here, we can give the deploymentID + Name = deployment.DeploymentName, + ExternalIdentifier = deployment.DeploymentId + }; + deployments.Add(iotEdgeModel); + } } } + nextToken = response.NextToken; } + while (!string.IsNullOrEmpty(nextToken)); + return deployments; } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs deleted file mode 100644 index 08ca42ce0..000000000 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs +++ /dev/null @@ -1,199 +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 AzureIoTHub.Portal.Infrastructure.Jobs.AWS -{ - using System.Linq; - using System.Net; - using System.Threading.Tasks; - using Amazon.GreengrassV2; - 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; - - [DisallowConcurrentExecution] - public class SyncGreenGrassDevicesJob : IJob - { - - private readonly ILogger logger; - private readonly IMapper mapper; - private readonly IUnitOfWork unitOfWork; - private readonly IEdgeDeviceRepository edgeDeviceRepository; - private readonly IEdgeDeviceModelRepository edgeDeviceModelRepository; - private readonly IDeviceTagValueRepository deviceTagValueRepository; - private readonly IAmazonIoT amazonIoTClient; - private readonly IAmazonGreengrassV2 amazonGreenGrass; - private readonly IConfigService configService; - private readonly IAWSExternalDeviceService awsExternalDevicesService; - - public SyncGreenGrassDevicesJob( - ILogger logger, - IMapper mapper, - IUnitOfWork unitOfWork, - IEdgeDeviceRepository edgeDeviceRepository, - IEdgeDeviceModelRepository edgeDeviceModelRepository, - IDeviceTagValueRepository deviceTagValueRepository, - IAmazonIoT amazonIoTClient, - IAmazonGreengrassV2 amazonGreenGrass, - IConfigService configService, - IAWSExternalDeviceService awsExternalDevicesService) - { - this.mapper = mapper; - this.unitOfWork = unitOfWork; - this.edgeDeviceRepository = edgeDeviceRepository; - this.edgeDeviceModelRepository = edgeDeviceModelRepository; - this.deviceTagValueRepository = deviceTagValueRepository; - this.amazonIoTClient = amazonIoTClient; - this.amazonGreenGrass = amazonGreenGrass; - this.configService = configService; - this.awsExternalDevicesService = awsExternalDevicesService; - this.logger = logger; - } - - - public async Task Execute(IJobExecutionContext context) - { - try - { - this.logger.LogInformation("Start of sync GreenGrass Devices job"); - - await SyncGreenGrassDevicesAsEdgeDevices(); - - this.logger.LogInformation("End of sync GreenGrass Devices job"); - } - catch (Exception e) - { - this.logger.LogError(e, "Sync GreenGrass Devices job has failed"); - } - } - - private async Task SyncGreenGrassDevicesAsEdgeDevices() - { - var things = await GetAllGreenGrassDevices(); - - foreach (var thing in things) - { - //Thing error - if (thing.HttpStatusCode != HttpStatusCode.OK) - { - this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error in the Amazon IoT API : {thing.HttpStatusCode}"); - continue; - } - - //ThingType not specified - if (thing.ThingTypeName.IsNullOrWhiteSpace()) - { - this.logger.LogInformation($"Cannot import Greengrass device '{thing.ThingName}' since it doesn't have related thing type."); - continue; - } - - //EdgeDeviceModel not find in DB - var edgeDeviceModel = await this.edgeDeviceModelRepository.GetByNameAsync(thing.ThingTypeName); - if (edgeDeviceModel == null) - { - this.logger.LogWarning($"Cannot import Greengrass device '{thing.ThingName}'. The EdgeDeviceModel '{thing.ThingTypeName}' doesn't exist"); - 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(edgeDevice); - } - - foreach (var item in (await this.edgeDeviceRepository.GetAllAsync( - edgeDevice => !things.Select(x => x.ThingId).Contains(edgeDevice.Id), - default, - d => d.Tags, - d => d.Labels - ))) - { - this.edgeDeviceRepository.Delete(item.Id); - } - - await this.unitOfWork.SaveAsync(); - } - - private async Task> GetAllGreenGrassDevices() - { - var devices = new List(); - - var nextToken = string.Empty; - - var response = await amazonGreenGrass.ListCoreDevicesAsync( - new ListCoreDevicesRequest - { - NextToken = nextToken - }); - - foreach (var requestDescribeThing in response.CoreDevices.Select(device => new DescribeThingRequest { ThingName = device.CoreDeviceThingName })) - { - try - { - devices.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); - } - catch (AmazonIoTException e) - { - this.logger.LogWarning($"Cannot import Greengrass device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); - continue; - } - } - - return devices; - } - - private async Task CreateOrUpdateGreenGrassDevice(EdgeDevice edgeDevice) - { - var edgeDeviceEntity = await this.edgeDeviceRepository.GetByIdAsync(edgeDevice.Id, d => d.Tags); - - if (edgeDeviceEntity == null) - { - await this.edgeDeviceRepository.InsertAsync(edgeDevice); - } - else - { - if (edgeDeviceEntity.Version >= edgeDevice.Version) return; - - foreach (var deviceTagEntity in edgeDeviceEntity.Tags) - { - this.deviceTagValueRepository.Delete(deviceTagEntity.Id); - } - - _ = this.mapper.Map(edgeDevice, edgeDeviceEntity); - this.edgeDeviceRepository.Update(edgeDeviceEntity); - } - } - } -} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs index 1533f80df..17eda60f3 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs @@ -8,6 +8,7 @@ namespace AzureIoTHub.Portal.Infrastructure.Jobs.AWS using Amazon.IoT.Model; using AutoMapper; using AzureIoTHub.Portal.Application.Managers; + using AzureIoTHub.Portal.Application.Services.AWS; using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Repositories; @@ -24,6 +25,7 @@ public class SyncThingTypesJob : IJob private readonly IDeviceModelRepository deviceModelRepository; private readonly IAmazonIoT amazonIoTClient; private readonly IDeviceModelImageManager deviceModelImageManager; + private readonly IAWSExternalDeviceService awsExternalDeviceService; public SyncThingTypesJob( ILogger logger, @@ -31,13 +33,15 @@ public SyncThingTypesJob( IUnitOfWork unitOfWork, IDeviceModelRepository deviceModelRepository, IAmazonIoT amazonIoTClient, - IDeviceModelImageManager awsImageManager) + IDeviceModelImageManager awsImageManager, + IAWSExternalDeviceService awsExternalDeviceService) { this.deviceModelImageManager = awsImageManager; this.mapper = mapper; this.unitOfWork = unitOfWork; this.deviceModelRepository = deviceModelRepository; this.amazonIoTClient = amazonIoTClient; + this.awsExternalDeviceService = awsExternalDeviceService; this.logger = logger; } @@ -64,7 +68,7 @@ private async Task SyncThingTypesAsDeviceModels() foreach (var thingType in thingTypes) { - var isEdge = await IsEdgeThingType(thingType); + var isEdge = await awsExternalDeviceService.IsEdgeThingType(thingType); // Cannot know if the thing type was created for an iotEdge or not, so skipping... if (!isEdge.HasValue) @@ -72,11 +76,7 @@ private async Task SyncThingTypesAsDeviceModels() continue; } - if (isEdge == true) - { - // TODO: Implement CreateOrUpdateEdgeModel here. - } - else + if (isEdge == false) { await CreateOrUpdateDeviceModel(thingType); } @@ -86,38 +86,6 @@ private async Task SyncThingTypesAsDeviceModels() await DeleteThingTypes(thingTypes); } - private 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); - } - private async Task> GetAllThingTypes() { var thingTypes = new List(); diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs index 4ba7d346a..9b1d90a11 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs @@ -6,11 +6,14 @@ namespace AzureIoTHub.Portal.Infrastructure.Jobs.AWS using System.Linq; using System.Net; using System.Threading.Tasks; + using Amazon.GreengrassV2.Model; + using Amazon.GreengrassV2; using Amazon.IoT; using Amazon.IoT.Model; using Amazon.IotData; using Amazon.IotData.Model; using AutoMapper; + using AzureIoTHub.Portal.Application.Services.AWS; using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Repositories; @@ -26,29 +29,41 @@ public class SyncThingsJob : IJob private readonly IMapper mapper; private readonly IUnitOfWork unitOfWork; private readonly IDeviceRepository deviceRepository; + private readonly IEdgeDeviceRepository edgeDeviceRepository; private readonly IDeviceModelRepository deviceModelRepository; + private readonly IEdgeDeviceModelRepository edgeDeviceModelRepository; private readonly IDeviceTagValueRepository deviceTagValueRepository; private readonly IAmazonIoT amazonIoTClient; private readonly IAmazonIotData amazonIoTDataClient; + private readonly IAmazonGreengrassV2 amazonGreenGrass; + private readonly IAWSExternalDeviceService awsExternalDeviceService; public SyncThingsJob( ILogger logger, IMapper mapper, IUnitOfWork unitOfWork, IDeviceRepository deviceRepository, + IEdgeDeviceRepository edgeDeviceRepository, IDeviceModelRepository deviceModelRepository, + IEdgeDeviceModelRepository edgeDeviceModelRepository, IDeviceTagValueRepository deviceTagValueRepository, IAmazonIoT amazonIoTClient, - IAmazonIotData amazonIoTDataClient) + IAmazonIotData amazonIoTDataClient, + IAmazonGreengrassV2 amazonGreenGrass, + IAWSExternalDeviceService awsExternalDeviceService) { this.mapper = mapper; this.unitOfWork = unitOfWork; this.deviceRepository = deviceRepository; + this.edgeDeviceRepository = edgeDeviceRepository; this.deviceModelRepository = deviceModelRepository; + this.edgeDeviceModelRepository = edgeDeviceModelRepository; this.deviceTagValueRepository = deviceTagValueRepository; this.amazonIoTClient = amazonIoTClient; this.amazonIoTDataClient = amazonIoTDataClient; + this.amazonGreenGrass = amazonGreenGrass; this.logger = logger; + this.awsExternalDeviceService = awsExternalDeviceService; } @@ -88,41 +103,104 @@ private async Task SyncThingsAsDevices() continue; } - //DeviceModel not find in DB - var deviceModel = await this.deviceModelRepository.GetByNameAsync(thing.ThingTypeName); - if (deviceModel == null) + bool? isEdge; + //Retrieve ThingType to know if it's an iotEdge + try { - this.logger.LogWarning($"Cannot import device '{thing.ThingName}'. The ThingType '{thing.ThingTypeName}' doesn't exist"); + var thingType = await this.amazonIoTClient.DescribeThingTypeAsync(new DescribeThingTypeRequest() + { + ThingTypeName = thing.ThingTypeName + }); + + isEdge = await awsExternalDeviceService.IsEdgeThingType(thingType); + } + catch (AmazonIoTException e) + { + this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT Data API.", e); continue; } - //ThingShadow not specified - var thingShadowRequest = new GetThingShadowRequest() + // Cannot know if the thing type was created for an iotEdge or not, so skipping... + if (!isEdge.HasValue) { - ThingName = thing.ThingName - }; - try + continue; + } + + // EdgeDevice + if (isEdge == true) { - var thingShadow = await this.amazonIoTDataClient.GetThingShadowAsync(thingShadowRequest); - if (thingShadow.HttpStatusCode != HttpStatusCode.OK) + //EdgeDeviceModel not find in DB + var edgeDeviceModel = await this.edgeDeviceModelRepository.GetByNameAsync(thing.ThingTypeName); + if (edgeDeviceModel == null) { - if (thingShadow.HttpStatusCode.Equals(HttpStatusCode.NotFound)) - this.logger.LogInformation($"Cannot import device '{thing.ThingName}' since it doesn't have related classic thing shadow"); - else - this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT API : {thingShadow.HttpStatusCode}"); + this.logger.LogWarning($"Cannot import Edge device '{thing.ThingName}'. The EdgeDeviceModel '{thing.ThingTypeName}' doesn't exist"); continue; } + + //EdgeDevice map + var edgeDevice = this.mapper.Map(thing); + edgeDevice.DeviceModelId = edgeDeviceModel.Id; + //Get EdgeDevice ConnectionState + try + { + var coreDevice = await amazonGreenGrass.GetCoreDeviceAsync(new GetCoreDeviceRequest() { CoreDeviceThingName = thing.ThingName }); + edgeDevice.ConnectionState = coreDevice.HttpStatusCode != HttpStatusCode.OK + ? "Disconnected" + : coreDevice.Status == CoreDeviceStatus.HEALTHY ? "Connected" : "Disconnected"; + } + //Disconnected if unable to retrieve + catch (AmazonGreengrassV2Exception) + { + edgeDevice.ConnectionState = "Disconnected"; + } + + //Create or update the edge device + await CreateOrUpdateEdgeDevice(edgeDevice); } - catch (AmazonIotDataException e) + //Device + else { - this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT Data API.", e); - continue; - } + //DeviceModel not find in DB + var deviceModel = await this.deviceModelRepository.GetByNameAsync(thing.ThingTypeName); + if (deviceModel == null) + { + this.logger.LogWarning($"Cannot import device '{thing.ThingName}'. The ThingType '{thing.ThingTypeName}' doesn't exist"); + continue; + } - //Create or update the thing - await CreateOrUpdateThing(thing, deviceModel); + //ThingShadow not specified + var thingShadowRequest = new GetThingShadowRequest() + { + ThingName = thing.ThingName + }; + try + { + var thingShadow = await this.amazonIoTDataClient.GetThingShadowAsync(thingShadowRequest); + if (thingShadow.HttpStatusCode != HttpStatusCode.OK) + { + if (thingShadow.HttpStatusCode.Equals(HttpStatusCode.NotFound)) + this.logger.LogInformation($"Cannot import device '{thing.ThingName}' since it doesn't have related classic thing shadow"); + else + this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT API : {thingShadow.HttpStatusCode}"); + continue; + } + } + catch (AmazonIotDataException e) + { + this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT Data API.", e); + continue; + } + + //Device + var device = this.mapper.Map(thing); + device.DeviceModelId = deviceModel.Id; + + //Create or update the thing + await CreateOrUpdateDevice(device); + } } + //Delete Device don't exist on AWS foreach (var item in (await this.deviceRepository.GetAllAsync( device => !things.Select(x => x.ThingId).Contains(device.Id), default, @@ -133,6 +211,17 @@ private async Task SyncThingsAsDevices() this.deviceRepository.Delete(item.Id); } + //Delete Edge Device don't exist on AWS + foreach (var item in (await this.edgeDeviceRepository.GetAllAsync( + device => !things.Select(x => x.ThingId).Contains(device.Id), + default, + d => d.Tags, + d => d.Labels + ))) + { + this.edgeDeviceRepository.Delete(item.Id); + } + await this.unitOfWork.SaveAsync(); } @@ -140,29 +229,40 @@ private async Task> GetAllThings() { var things = new List(); - var response = await amazonIoTClient.ListThingsAsync(); + var marker = string.Empty; - foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName })) + do { - try + var request = new ListThingsRequest { - things.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); - } - catch (AmazonIoTException e) + Marker = marker + }; + + var response = await amazonIoTClient.ListThingsAsync(request); + + foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName })) { - this.logger.LogWarning($"Cannot import device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); - continue; + 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; } - private async Task CreateOrUpdateThing(DescribeThingResponse thing, DeviceModel deviceModel) + private async Task CreateOrUpdateDevice(Device device) { - var device = this.mapper.Map(thing); var deviceEntity = await this.deviceRepository.GetByIdAsync(device.Id, d => d.Tags); - device.DeviceModelId = deviceModel.Id; if (deviceEntity == null) { @@ -181,5 +281,27 @@ private async Task CreateOrUpdateThing(DescribeThingResponse thing, DeviceModel this.deviceRepository.Update(deviceEntity); } } + + private async Task CreateOrUpdateEdgeDevice(EdgeDevice edgeDevice) + { + var edgeDeviceEntity = await this.edgeDeviceRepository.GetByIdAsync(edgeDevice.Id, d => d.Tags); + + if (edgeDeviceEntity == null) + { + await this.edgeDeviceRepository.InsertAsync(edgeDevice); + } + else + { + if (edgeDeviceEntity.Version >= edgeDevice.Version) return; + + foreach (var deviceTagEntity in edgeDeviceEntity.Tags) + { + this.deviceTagValueRepository.Delete(deviceTagEntity.Id); + } + + _ = this.mapper.Map(edgeDevice, edgeDeviceEntity); + this.edgeDeviceRepository.Update(edgeDeviceEntity); + } + } } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs index 59d69df36..dc026c326 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSExternalDeviceService.cs @@ -145,5 +145,37 @@ public async Task GetEdgeDeviceNbDevices(IoTEdgeDevice device) ? 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); + } } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs b/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs index 390aacc4f..7b67ec3dd 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs @@ -102,14 +102,6 @@ private static IServiceCollection ConfigureAWSSyncJobs(this IServiceCollection s .WithSimpleSchedule(s => s .WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes) .RepeatForever())); - - _ = q.AddJob(j => j.WithIdentity(nameof(SyncGreenGrassDevicesJob))) - .AddTrigger(t => t - .WithIdentity($"{nameof(SyncGreenGrassDevicesJob)}") - .ForJob(nameof(SyncGreenGrassDevicesJob)) - .WithSimpleSchedule(s => s - .WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes) - .RepeatForever())); }); } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs deleted file mode 100644 index 71ce10f32..000000000 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs +++ /dev/null @@ -1,597 +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 AzureIoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS -{ - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - using System.Net; - using System.Threading; - using System.Threading.Tasks; - using Amazon.GreengrassV2; - using Amazon.GreengrassV2.Model; - 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; - using AzureIoTHub.Portal.Infrastructure.Jobs.AWS; - using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using NUnit.Framework; - using Quartz; - using AzureIoTHub.Portal.Models.v10; - - public class SyncGreenGrassDevicesJobTests : BackendUnitTest - { - private IJob syncGreenGrassDevicesJob; - - private Mock amazonIoTClient; - private Mock amazonGreenGrass; - private Mock mockUnitOfWork; - private Mock mockDeviceRepository; - private Mock mockDeviceModelRepository; - private Mock mockDeviceTagValueRepository; - private Mock mockConfigService; - private Mock mockAwsExternalDevicesService; - - public override void Setup() - { - base.Setup(); - - this.mockUnitOfWork = MockRepository.Create(); - this.mockDeviceRepository = MockRepository.Create(); - this.mockDeviceModelRepository = MockRepository.Create(); - 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); - _ = ServiceCollection.AddSingleton(this.mockDeviceModelRepository.Object); - _ = 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(); - - - Services = ServiceCollection.BuildServiceProvider(); - - this.syncGreenGrassDevicesJob = Services.GetRequiredService(); - } - - [Test] - public async Task ExecuteNewEdgeDeviceEdgeDeviceCreated() - { - // Arrange - var mockJobExecutionContext = MockRepository.Create(); - - var expectedDeviceModel = Fixture.Create(); - var newDevice = 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 = newDevice.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 = newDevice.Id, - ThingName = newDevice.Name, - ThingTypeName = newDevice.DeviceModel.Name, - Version = newDevice.Version, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = this.mockDeviceModelRepository - .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); - - _ = this.mockDeviceRepository.Setup(repository => repository.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - - _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) - .ReturnsAsync(new List - { - new EdgeDevice - { - Id = Guid.NewGuid().ToString() - } - }); - - this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); - - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - // Act - await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() - { - // 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, - Tags = new List() - { - new() - { - Id = Fixture.Create(), - Name = Fixture.Create(), - Value = Fixture.Create() - } - } - }; - - 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 = 2, - 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.OK }); - - _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) - .ReturnsAsync(existingDevice); - - this.mockDeviceTagValueRepository.Setup(repository => repository.Delete(It.IsAny())).Verifiable(); - - this.mockDeviceRepository.Setup(repository => repository.Update(It.IsAny())).Verifiable(); - - _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) - .ReturnsAsync(new List - { - new EdgeDevice - { - Id = Guid.NewGuid().ToString() - } - }); - - this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); - - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - // Act - await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public async Task ExecuteExistingEdgeDeviceWithOlderVersionEdgeDeviceNotUpdated() - { - // 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 = 2, - Tags = new List() - { - new() - { - Id = Fixture.Create(), - Name = Fixture.Create(), - Value = Fixture.Create() - } - } - }; - - 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.OK }); - - _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) - .ReturnsAsync(existingDevice); - - _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) - .ReturnsAsync(new List - { - new EdgeDevice - { - Id = Guid.NewGuid().ToString() - } - }); - - this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); - - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - // Act - await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public async Task ExecuteNewEdgeDeviceWithDescribeThingErrorSkipped() - { - // 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 = 2 - }; - - 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() - { - 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 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() - { - // 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 = 2 - }; - - 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, - Version = 1, - HttpStatusCode = HttpStatusCode.OK - }); - - _ = 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 ExecuteNewDeviceWithUnknownThingTypeSkipped() - { - // 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 = 2 - }; - - 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((EdgeDeviceModel)null); - - _ = 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(); - } - - public async Task ExecuteNewEdgeDeviceWithAmazonIotExceptionSkipped() - { - // 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 = 2 - }; - - 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())) - .Throws(new AmazonIoTException("")); - - _ = 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(); - } - } -} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs index 72ce13cfc..c1431de4b 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs @@ -11,7 +11,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS using Amazon.IoT; using Amazon.IoT.Model; using AutoFixture; - using AzureIoTHub.Portal.Application.Managers; + using AzureIoTHub.Portal.Application.Managers; + using AzureIoTHub.Portal.Application.Services.AWS; using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Repositories; @@ -27,7 +28,8 @@ public class SyncThingTypesJobTests : BackendUnitTest private IJob syncThingTypesJob; private Mock iaAmazon; - private Mock mockAWSImageManager; + private Mock mockAWSImageManager; + private Mock mockAWSExternalDeviceService; private Mock mockUnitOfWork; private Mock mockDeviceModelRepository; @@ -35,12 +37,14 @@ public override void Setup() { base.Setup(); - this.mockAWSImageManager = MockRepository.Create(); + this.mockAWSImageManager = MockRepository.Create(); + this.mockAWSExternalDeviceService = MockRepository.Create(); this.mockUnitOfWork = MockRepository.Create(); this.mockDeviceModelRepository = MockRepository.Create(); this.iaAmazon = MockRepository.Create(); - _ = ServiceCollection.AddSingleton(this.mockAWSImageManager.Object); + _ = ServiceCollection.AddSingleton(this.mockAWSImageManager.Object); + _ = ServiceCollection.AddSingleton(this.mockAWSExternalDeviceService.Object); _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceModelRepository.Object); _ = ServiceCollection.AddSingleton(this.iaAmazon.Object); @@ -120,19 +124,8 @@ public async Task Execute_SyncNewAndExistingAndDepprecatedThingTypes_DeviceModel _ = this.iaAmazon.Setup(client => client.DescribeThingTypeAsync(It.Is(c => c.ThingTypeName == depcrecatedThingType.ThingTypeName), It.IsAny())) .ReturnsAsync(depcrecatedThingType); - _ = this.iaAmazon.Setup(client => client.ListTagsForResourceAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new ListTagsForResourceResponse - { - NextToken = Fixture.Create(), - Tags = new List - { - new Tag - { - Key = "iotEdge", - Value = "False" - } - } - }); + _ = this.mockAWSExternalDeviceService.Setup(client => client.IsEdgeThingType(It.IsAny())) + .ReturnsAsync(false); var existingDeviceModel = new DeviceModel { diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs index f7ed62260..b2b4ef9d3 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs @@ -9,11 +9,14 @@ namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS using System.Net; using System.Threading; using System.Threading.Tasks; + using Amazon.GreengrassV2; + using Amazon.GreengrassV2.Model; using Amazon.IoT; using Amazon.IoT.Model; using Amazon.IotData; using Amazon.IotData.Model; using AutoFixture; + using AzureIoTHub.Portal.Application.Services.AWS; using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Repositories; @@ -30,10 +33,14 @@ public class SyncThingsJobTests : BackendUnitTest private Mock amazonIoTClient; private Mock amazonIoTDataClient; + private Mock amazonGreenGrass; private Mock mockUnitOfWork; private Mock mockDeviceRepository; + private Mock mockEdgeDeviceRepository; private Mock mockDeviceModelRepository; + private Mock mockEdgeDeviceModelRepository; private Mock mockDeviceTagValueRepository; + private Mock awsExternalDeviceService; public override void Setup() { @@ -41,17 +48,25 @@ public override void Setup() this.mockUnitOfWork = MockRepository.Create(); this.mockDeviceRepository = MockRepository.Create(); + this.mockEdgeDeviceRepository = MockRepository.Create(); this.mockDeviceModelRepository = MockRepository.Create(); + this.mockEdgeDeviceModelRepository = MockRepository.Create(); this.mockDeviceTagValueRepository = MockRepository.Create(); this.amazonIoTClient = MockRepository.Create(); this.amazonIoTDataClient = MockRepository.Create(); + this.amazonGreenGrass = MockRepository.Create(); + this.awsExternalDeviceService = MockRepository.Create(); _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockEdgeDeviceRepository.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceModelRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockEdgeDeviceModelRepository.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceTagValueRepository.Object); _ = ServiceCollection.AddSingleton(this.amazonIoTClient.Object); _ = ServiceCollection.AddSingleton(this.amazonIoTDataClient.Object); + _ = ServiceCollection.AddSingleton(this.amazonGreenGrass.Object); + _ = ServiceCollection.AddSingleton(this.awsExternalDeviceService.Object); _ = ServiceCollection.AddSingleton(); @@ -87,7 +102,7 @@ public async Task ExecuteNewDeviceDeviceCreated() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -100,6 +115,17 @@ public async Task ExecuteNewDeviceDeviceCreated() HttpStatusCode = HttpStatusCode.OK }); + _ = 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())) + .ReturnsAsync(false); + _ = this.mockDeviceModelRepository .Setup(x => x.GetByNameAsync(newDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); @@ -124,6 +150,17 @@ public async Task ExecuteNewDeviceDeviceCreated() this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + _ = this.mockEdgeDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new EdgeDevice + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockEdgeDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) .Returns(Task.CompletedTask); @@ -170,7 +207,7 @@ public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -183,6 +220,17 @@ public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() HttpStatusCode = HttpStatusCode.OK }); + _ = 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())) + .ReturnsAsync(false); + _ = this.mockDeviceModelRepository .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); @@ -200,6 +248,9 @@ public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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); @@ -237,7 +288,7 @@ public async Task ExecuteExistingDeviceWithOlderVersionDeviceNotUpdated() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -250,6 +301,17 @@ public async Task ExecuteExistingDeviceWithOlderVersionDeviceNotUpdated() HttpStatusCode = HttpStatusCode.OK }); + _ = 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())) + .ReturnsAsync(false); + _ = this.mockDeviceModelRepository .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); @@ -263,6 +325,9 @@ public async Task ExecuteExistingDeviceWithOlderVersionDeviceNotUpdated() _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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); @@ -300,7 +365,7 @@ public async Task ExecuteNewDeviceWithDescribeThingErrorSkipped() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -312,6 +377,9 @@ public async Task ExecuteNewDeviceWithDescribeThingErrorSkipped() _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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); @@ -349,7 +417,62 @@ public async Task ExecuteNewDeviceWithoutThingTypeSkipped() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + Version = 1, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockEdgeDeviceRepository.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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewDeviceWithUnknownIsEdgeTagSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new Device + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = existingDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -357,13 +480,28 @@ public async Task ExecuteNewDeviceWithoutThingTypeSkipped() { ThingId = existingDevice.Id, ThingName = existingDevice.Name, + ThingTypeName = existingDevice.DeviceModel.Name, Version = 1, HttpStatusCode = HttpStatusCode.OK }); + _ = 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())) + .ReturnsAsync((bool?)null); + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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); @@ -375,7 +513,7 @@ public async Task ExecuteNewDeviceWithoutThingTypeSkipped() } [Test] - public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() + public async Task ExecuteNewDeviceWithUnknownDeviceModelSkipped() { // Arrange var mockJobExecutionContext = MockRepository.Create(); @@ -401,7 +539,7 @@ public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -414,6 +552,17 @@ public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() HttpStatusCode = HttpStatusCode.OK }); + _ = 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())) + .ReturnsAsync(false); + _ = this.mockDeviceModelRepository .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) .ReturnsAsync((DeviceModel)null); @@ -421,6 +570,9 @@ public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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); @@ -459,7 +611,7 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped(HttpStatusCode thing } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -472,6 +624,17 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped(HttpStatusCode thing HttpStatusCode = HttpStatusCode.OK }); + _ = 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())) + .ReturnsAsync(false); + _ = this.mockDeviceModelRepository .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); @@ -482,6 +645,9 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped(HttpStatusCode thing _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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); @@ -492,6 +658,7 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped(HttpStatusCode thing MockRepository.VerifyAll(); } + [Test] public async Task ExecuteNewDeviceWithAmazonIotExceptionSkipped() { // Arrange @@ -518,7 +685,7 @@ public async Task ExecuteNewDeviceWithAmazonIotExceptionSkipped() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -527,6 +694,9 @@ public async Task ExecuteNewDeviceWithAmazonIotExceptionSkipped() _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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); @@ -537,6 +707,7 @@ public async Task ExecuteNewDeviceWithAmazonIotExceptionSkipped() MockRepository.VerifyAll(); } + [Test] public async Task ExecuteNewDeviceWithAmazonIotDataExceptionSkipped() { // Arrange @@ -563,7 +734,7 @@ public async Task ExecuteNewDeviceWithAmazonIotDataExceptionSkipped() } }; - _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(thingsListing); _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) @@ -576,6 +747,17 @@ public async Task ExecuteNewDeviceWithAmazonIotDataExceptionSkipped() HttpStatusCode = HttpStatusCode.OK }); + _ = 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())) + .ReturnsAsync(false); + _ = this.mockDeviceModelRepository .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) .ReturnsAsync(expectedDeviceModel); @@ -586,6 +768,439 @@ public async Task ExecuteNewDeviceWithAmazonIotDataExceptionSkipped() _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); + _ = this.mockEdgeDeviceRepository.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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewEdgeDeviceEdgeDeviceCreated() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var newDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 1 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = newDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = newDevice.Id, + ThingName = newDevice.Name, + ThingTypeName = newDevice.DeviceModel.Name, + Version = newDevice.Version, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = 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())) + .ReturnsAsync(true); + + _ = this.mockEdgeDeviceModelRepository + .Setup(x => x.GetByNameAsync(newDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + + _ = this.mockEdgeDeviceRepository.Setup(repository => repository.GetByIdAsync(newDevice.Id, d => d.Tags)) + .ReturnsAsync((EdgeDevice)null); + + _ = this.mockEdgeDeviceRepository.Setup(repository => repository.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new Device + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + + _ = this.mockEdgeDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new EdgeDevice + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockEdgeDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteExistingEdgeDeviceWithHigherVersionEdgeDeviceUpdated() + { + // 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, + Tags = new List() + { + new() + { + Id = Fixture.Create(), + Name = Fixture.Create(), + Value = Fixture.Create() + } + } + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = existingDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + ThingTypeName = existingDevice.DeviceModel.Name, + Version = 2, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = 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())) + .ReturnsAsync(true); + + _ = this.mockEdgeDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK, Status = CoreDeviceStatus.HEALTHY }); + + _ = this.mockEdgeDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) + .ReturnsAsync(existingDevice); + + this.mockDeviceTagValueRepository.Setup(repository => repository.Delete(It.IsAny())).Verifiable(); + + this.mockEdgeDeviceRepository.Setup(repository => repository.Update(It.IsAny())).Verifiable(); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockEdgeDeviceRepository.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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteExistingEdgeDeviceWithOlderVersionEdgeDeviceNotUpdated() + { + // 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 = 2 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = existingDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = 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.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())) + .ReturnsAsync(true); + + _ = this.mockEdgeDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + + _ = this.mockEdgeDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) + .ReturnsAsync(existingDevice); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockEdgeDeviceRepository.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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewEdgeDeviceWithUnknownEdgeDeviceModelSkipped() + { + // 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 = 2 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = existingDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = 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.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())) + .ReturnsAsync(true); + + _ = this.mockEdgeDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync((EdgeDeviceModel)null); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockEdgeDeviceRepository.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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewEdgeDeviceWithoutCoreDeviceDeviceCreatedWithDisconnectedState() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var newDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 1 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = newDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = newDevice.Id, + ThingName = newDevice.Name, + ThingTypeName = newDevice.DeviceModel.Name, + Version = newDevice.Version, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = 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())) + .ReturnsAsync(true); + + _ = this.mockEdgeDeviceModelRepository + .Setup(x => x.GetByNameAsync(newDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.NotFound }); + + _ = this.mockEdgeDeviceRepository.Setup(repository => repository.GetByIdAsync(newDevice.Id, d => d.Tags)) + .ReturnsAsync((EdgeDevice)null); + + _ = this.mockEdgeDeviceRepository.Setup(repository => repository.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new Device + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + + _ = this.mockEdgeDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new EdgeDevice + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockEdgeDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) .Returns(Task.CompletedTask); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSExternalDeviceServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSExternalDeviceServiceTest.cs index 1dbc33daa..003bf9b4b 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSExternalDeviceServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSExternalDeviceServiceTest.cs @@ -414,5 +414,130 @@ public Task UpdateDeviceShadowShouldThrowInternalServerErrorIfHttpStatusCodeIsNo MockRepository.VerifyAll(); return Task.CompletedTask; } + + [Test] + public async Task IsEdgeThingTypeReturnTrue() + { + // Arrange + var thingType = new DescribeThingTypeResponse() + { + ThingTypeArn = Fixture.Create(), + ThingTypeId = Fixture.Create(), + ThingTypeName = Fixture.Create(), + ThingTypeMetadata = new ThingTypeMetadata() + }; + + _ = this.mockAmazonIotClient.Setup(client => client.ListTagsForResourceAsync(It.Is(c => c.ResourceArn == thingType.ThingTypeArn), It.IsAny())) + .ReturnsAsync(new ListTagsForResourceResponse + { + NextToken = Fixture.Create(), + Tags = new List + { + new Tag + { + Key = "iotEdge", + Value = "true" + } + } + }); + + //Act + var result = await this.awsExternalDeviceService.IsEdgeThingType(thingType); + + //Assert + _ = result.Should().BeTrue(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task IsNotEdgeThingTypeReturnFalse() + { + // Arrange + var thingType = new DescribeThingTypeResponse() + { + ThingTypeArn = Fixture.Create(), + ThingTypeId = Fixture.Create(), + ThingTypeName = Fixture.Create(), + ThingTypeMetadata = new ThingTypeMetadata() + }; + + _ = this.mockAmazonIotClient.Setup(client => client.ListTagsForResourceAsync(It.Is(c => c.ResourceArn == thingType.ThingTypeArn), It.IsAny())) + .ReturnsAsync(new ListTagsForResourceResponse + { + NextToken = Fixture.Create(), + Tags = new List + { + new Tag + { + Key = "iotEdge", + Value = "false" + } + } + }); + + //Act + var result = await this.awsExternalDeviceService.IsEdgeThingType(thingType); + + //Assert + _ = result.Should().BeFalse(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task EdgeThingTypeNullReturnNull() + { + // Arrange + var thingType = new DescribeThingTypeResponse() + { + ThingTypeArn = Fixture.Create(), + ThingTypeId = Fixture.Create(), + ThingTypeName = Fixture.Create(), + ThingTypeMetadata = new ThingTypeMetadata() + }; + + _ = this.mockAmazonIotClient.Setup(client => client.ListTagsForResourceAsync(It.Is(c => c.ResourceArn == thingType.ThingTypeArn), It.IsAny())) + .ReturnsAsync((ListTagsForResourceResponse)null); + + //Act + var result = await this.awsExternalDeviceService.IsEdgeThingType(thingType); + + //Assert + _ = result.Should().BeNull(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task EdgeThingTypeNotBooleanReturnNull() + { + // Arrange + var thingType = new DescribeThingTypeResponse() + { + ThingTypeArn = Fixture.Create(), + ThingTypeId = Fixture.Create(), + ThingTypeName = Fixture.Create(), + ThingTypeMetadata = new ThingTypeMetadata() + }; + + _ = this.mockAmazonIotClient.Setup(client => client.ListTagsForResourceAsync(It.Is(c => c.ResourceArn == thingType.ThingTypeArn), It.IsAny())) + .ReturnsAsync(new ListTagsForResourceResponse + { + NextToken = Fixture.Create(), + Tags = new List + { + new Tag + { + Key = "iotEdge", + Value = "123" + } + } + }); + + //Act + var result = await this.awsExternalDeviceService.IsEdgeThingType(thingType); + + //Assert + _ = result.Should().BeNull(); + MockRepository.VerifyAll(); + } } }