From a99d8942d1931fb3d30d64c37930d4c2018d9982 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 12:16:35 +0200 Subject: [PATCH 01/23] Add edge module id to store greengrass public components --- src/AzureIoTHub.Portal.Shared/Models/v1.0/IoTEdgeModule.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/AzureIoTHub.Portal.Shared/Models/v1.0/IoTEdgeModule.cs b/src/AzureIoTHub.Portal.Shared/Models/v1.0/IoTEdgeModule.cs index 643b710f5..4979faa26 100644 --- a/src/AzureIoTHub.Portal.Shared/Models/v1.0/IoTEdgeModule.cs +++ b/src/AzureIoTHub.Portal.Shared/Models/v1.0/IoTEdgeModule.cs @@ -11,7 +11,12 @@ namespace AzureIoTHub.Portal.Models.v10 /// IoT Edge module. /// public class IoTEdgeModule - { + { + /// + /// The module name, only used for AWS IoT Greengrass. + /// + public string Id { get; set; } = default!; + /// /// The module name. /// From 5be645042e36490429bde82fa6bb38a63c44390c Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 12:17:55 +0200 Subject: [PATCH 02/23] Add GetPublicEdgeModules --- .../Services/IConfigService.cs | 4 +- .../Services/IEdgeModelService.cs | 4 +- .../Services/EdgeModelClientService.cs | 7 +- .../Services/IEdgeModelClientService.cs | 4 +- .../Services/AWS/AwsConfigService.cs | 943 +++++++++--------- .../Services/EdgeModelService.cs | 6 +- .../Controllers/v1.0/EdgeModelsController.cs | 12 + .../Services/ConfigService.cs | 7 +- 8 files changed, 511 insertions(+), 476 deletions(-) diff --git a/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs b/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs index 047f797db..5b894a266 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs @@ -33,6 +33,8 @@ public interface IConfigService Task> GetModelSystemModule(string modelId); - Task> GetConfigRouteList(string modelId); + Task> GetConfigRouteList(string modelId); + + Task> GetPublicEdgeModules(); } } diff --git a/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs b/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs index 579ded29a..ba522ce24 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs @@ -26,6 +26,8 @@ public interface IEdgeModelService Task DeleteEdgeModelAvatar(string edgeModelId); - Task SaveModuleCommands(IoTEdgeModel deviceModelObject); + Task SaveModuleCommands(IoTEdgeModel deviceModelObject); + + Task> GetPublicEdgeModules(); } } diff --git a/src/AzureIoTHub.Portal.Client/Services/EdgeModelClientService.cs b/src/AzureIoTHub.Portal.Client/Services/EdgeModelClientService.cs index 289a6f8cb..de38c0820 100644 --- a/src/AzureIoTHub.Portal.Client/Services/EdgeModelClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/EdgeModelClientService.cs @@ -66,6 +66,11 @@ public Task ChangeAvatar(string id, MultipartFormDataContent content) public Task DeleteAvatar(string id) { return this.http.DeleteAsync($"{this.apiUrlBase}/{id}/avatar"); - } + } + + public async Task> GetPublicEdgeModules() + { + return await this.http.GetFromJsonAsync>($"{this.apiUrlBase}/public-modules") ?? new List(); + } } } diff --git a/src/AzureIoTHub.Portal.Client/Services/IEdgeModelClientService.cs b/src/AzureIoTHub.Portal.Client/Services/IEdgeModelClientService.cs index ab48d3c24..a29f9502f 100644 --- a/src/AzureIoTHub.Portal.Client/Services/IEdgeModelClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/IEdgeModelClientService.cs @@ -25,6 +25,8 @@ public interface IEdgeModelClientService Task ChangeAvatar(string id, MultipartFormDataContent content); - Task DeleteAvatar(string id); + Task DeleteAvatar(string id); + + Task> GetPublicEdgeModules(); } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs index c9fa19af8..4aa17e5fc 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs @@ -1,470 +1,473 @@ -// 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.Services.AWS -{ - using System.Collections.Generic; - using System.Net; - using System.Text; - using System.Threading.Tasks; - using Amazon.GreengrassV2; - using Amazon.GreengrassV2.Model; - using Amazon.IoT; - using Amazon.IoT.Model; - using AutoMapper; - using AzureIoTHub.Portal.Application.Services; - using AzureIoTHub.Portal.Domain; - using AzureIoTHub.Portal.Domain.Exceptions; - using AzureIoTHub.Portal.Domain.Repositories; - using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Shared.Models.v10; - using Newtonsoft.Json.Linq; - using Configuration = Microsoft.Azure.Devices.Configuration; - - public class AwsConfigService : IConfigService - { - private readonly IAmazonGreengrassV2 greengras; - private readonly IAmazonIoT iotClient; - - private readonly IUnitOfWork unitOfWork; - private readonly IEdgeDeviceModelRepository edgeModelRepository; - private readonly ConfigHandler config; - private readonly IMapper mapper; - - public AwsConfigService( - IAmazonGreengrassV2 greengras, - IAmazonIoT iot, - IMapper mapper, - IUnitOfWork unitOfWork, - IEdgeDeviceModelRepository edgeModelRepository, - ConfigHandler config) - { - this.greengras = greengras; - this.iotClient = iot; - this.mapper = mapper; - this.unitOfWork = unitOfWork; - this.edgeModelRepository = edgeModelRepository; - this.config = config; - } - - public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) - { - - var createDeploymentRequest = new CreateDeploymentRequest - { - DeploymentName = edgeModel?.Name, - Components = await CreateGreenGrassComponents(edgeModel!), - TargetArn = await GetThingGroupArn(edgeModel!) - }; - - var createDeploymentResponse = await this.greengras.CreateDeploymentAsync(createDeploymentRequest); - - if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) - { - throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); - - } - else - { - var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId!); - if (edgeModelEntity == null) - { - throw new Domain.Exceptions.ResourceNotFoundException($"The edge model with id {edgeModel?.ModelId} not found"); - - } - else - { - edgeModel!.ExternalIdentifier = createDeploymentResponse.DeploymentId; - - _ = this.mapper.Map(edgeModel, edgeModelEntity); - - this.edgeModelRepository.Update(edgeModelEntity); - await this.unitOfWork.SaveAsync(); - } - - } - } - - private async Task GetThingGroupArn(IoTEdgeModel edgeModel) - { - await CreateThingTypeIfNotExists(edgeModel!.Name); - - var dynamicThingGroup = new DescribeThingGroupRequest - { - ThingGroupName = edgeModel?.Name - }; - - try - { - var existingThingGroupResponse = await this.iotClient.DescribeThingGroupAsync(dynamicThingGroup); - - return existingThingGroupResponse.ThingGroupArn; - } - catch (Amazon.IoT.Model.ResourceNotFoundException) - { - var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest - { - ThingGroupName = edgeModel!.Name, - QueryString = $"thingTypeName: {edgeModel!.Name}" - }); - - return createThingGroupResponse.ThingGroupArn; - } - } - - private async Task CreateThingTypeIfNotExists(string thingTypeName) - { - var existingThingType = new DescribeThingTypeRequest - { - ThingTypeName = thingTypeName - }; - - try - { - _ = await this.iotClient.DescribeThingTypeAsync(existingThingType); - } - catch (Amazon.IoT.Model.ResourceNotFoundException) - { - _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest - { - ThingTypeName = thingTypeName, - Tags = new List - { - new Tag - { - Key = "iotEdge", - Value = "True" - } - } - }); - } - } - - private async Task> CreateGreenGrassComponents(IoTEdgeModel edgeModel) - { - var listcomponentName = new Dictionary(); - foreach (var component in edgeModel.EdgeModules) - { - try - { - _ = await this.greengras.DescribeComponentAsync(new DescribeComponentRequest - { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}" - }); - listcomponentName.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); - - } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) - { - var recipeJson = JsonCreateComponent(component); - var recipeBytes = Encoding.UTF8.GetBytes(recipeJson.ToString()); - var recipeStream = new MemoryStream(recipeBytes); - - var componentVersion = new CreateComponentVersionRequest - { - InlineRecipe = recipeStream - }; - var response = await greengras.CreateComponentVersionAsync(componentVersion); - if (response.HttpStatusCode != HttpStatusCode.Created) - { - throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API."); - - } - listcomponentName.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); - } - } - - - return listcomponentName; - } - - private static JObject JsonCreateComponent(IoTEdgeModule component) - { - - var environmentVariableObject = new JObject(); - - foreach (var env in component.EnvironmentVariables) - { - environmentVariableObject.Add(new JProperty(env.Name, env.Value)); - } - - var recipeJson =new JObject( - new JProperty("RecipeFormatVersion", "2020-01-25"), - new JProperty("ComponentName", component.ModuleName), - new JProperty("ComponentVersion", component.Version), - new JProperty("ComponentPublisher", "IotHub"), - new JProperty("ComponentDependencies", - new JObject( - new JProperty("aws.greengrass.DockerApplicationManager", - new JObject(new JProperty("VersionRequirement", "~2.0.0"))), - new JProperty("aws.greengrass.TokenExchangeService", - new JObject(new JProperty("VersionRequirement", "~2.0.0"))) - ) - ), - new JProperty("Manifests", - new JArray( - new JObject( - new JProperty("Platform", - new JObject(new JProperty("os", "linux"))), - new JProperty("Lifecycle", - new JObject(new JProperty("Run", $"docker run {component.ImageURI}"), - new JProperty("Environment",environmentVariableObject))), - new JProperty("Artifacts", - new JArray( - new JObject(new JProperty("URI", $"docker:{component.ImageURI}")) - ) - ) - ) - ) - ) - ); - - return recipeJson; - } - - //AWS Not implemented methods - - public Task> GetIoTEdgeConfigurations() - { - throw new NotImplementedException(); - } - - public Task> GetDevicesConfigurations() - { - throw new NotImplementedException(); - } - - public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) - { - throw new NotImplementedException(); - } - - public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) - { - throw new NotImplementedException(); - } - - public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) - { - throw new NotImplementedException(); - } - - public Task GetConfigItem(string id) - { - throw new NotImplementedException(); - } - - public async Task DeleteConfiguration(string modelId) - { - var modules = await GetConfigModuleList(modelId); - foreach (var module in modules) - { - var deletedComponentResponse = await this.greengras.DeleteComponentAsync(new DeleteComponentRequest - { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" - }); - - if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) - { - throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); - - } - } - - var cancelDeploymentResponse = await this.greengras.CancelDeploymentAsync(new CancelDeploymentRequest - { - DeploymentId = modelId - }); - if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) - { - throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); - - } - else - { - var deleteDeploymentResponse = await this.greengras.DeleteDeploymentAsync(new DeleteDeploymentRequest - { - DeploymentId = modelId - }); - - if (deleteDeploymentResponse.HttpStatusCode != HttpStatusCode.NoContent) - { - throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API."); - } - } - - } - - public Task GetFailedDeploymentsCount() - { - throw new NotImplementedException(); - } - - public async Task> GetConfigModuleList(string modelId) - { - - var moduleList = new List(); - - var getDeployement = new GetDeploymentRequest - { - DeploymentId = modelId, - }; - try - { - var response = await this.greengras.GetDeploymentAsync(getDeployement); - - foreach (var compoenent in response.Components) - { - try - { - var responseComponent = await this.greengras.GetComponentAsync(new GetComponentRequest - { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", - RecipeOutputFormat = RecipeOutputFormat.JSON - }); - - // Read the Recipe which is in JSON Format - using var reader = new StreamReader(responseComponent.Recipe); - var recipeJsonString = reader.ReadToEnd(); - - // Extract the imageUri from the 'Run' JSON object - var uriImage = retreiveImageUri("Lifecycle", "Run", recipeJsonString); - // Extract the environment Variables from the 'Environment' JSON object - var env = retreiveEnvVariableAttr("Lifecycle", "Environment", recipeJsonString); - - var iotEdgeModule = new IoTEdgeModule - { - ModuleName = compoenent.Key, - ImageURI = uriImage, - EnvironmentVariables = env, - Version = compoenent.Value.ComponentVersion - }; - - moduleList.Add(iotEdgeModule); - } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) - { - throw new InternalServerErrorException($"The component {compoenent.Key} is not found or may not be a docker component or may be a public component"); - - } - - - } - return moduleList; - } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) - { - throw new InternalServerErrorException("The deployment is not found"); - - } - } - - private static string retreiveImageUri(string parent, string child, string recipeJsonString) - { - var uriImage = ""; - // Parse the string as a JSON object - var recipeJsonObject = JObject.Parse(recipeJsonString); - - // Extract the "Manifests" array - var jArray = recipeJsonObject["Manifests"] as JArray; - var manifests = jArray; - - if (manifests != null && manifests.Count > 0) - { - // Get the first manifest in the array - var firstManifest = manifests[0] as JObject; - - // Extract the "Lifecycle" object - var jObject = firstManifest?[parent] as JObject; - var lifecycle = jObject; - - if (lifecycle != null) - { - // Extract the value of "Run" - var runObject = lifecycle[child] as JObject; - var runValueObject = runObject; - if (runValueObject != null) - { - var runValue = runValueObject.ToString(); - // Search the index of the 1st whitespace - var firstSpaceIndex = runValue.IndexOf(' ', StringComparison.Ordinal); - - if (firstSpaceIndex != -1) - { - // // Search the index of the 2nd whitespace - var secondSpaceIndex = runValue.IndexOf(' ', firstSpaceIndex + 1); - - if (secondSpaceIndex != -1) - { - // Extract the URI iamge - uriImage = runValue[(secondSpaceIndex + 1)..]; - } - - } - } - } - } - - return uriImage; - } - - private static List retreiveEnvVariableAttr(string parent, string child, string recipeJsonString) - { - - // Parse the string as a JSON object - var recipeJsonObject = JObject.Parse(recipeJsonString); - - var environmentVariables = new List(); - - // Extract the "Manifests" array - var jArray = recipeJsonObject["Manifests"] as JArray; - var manifests = jArray; - - if (manifests != null && manifests.Count > 0) - { - // Get the first manifest in the array - var firstManifest = manifests[0] as JObject; - - // Extract the "Lifecycle" object - var jObject = firstManifest?[parent] as JObject; - var lifecycle = jObject; - - if (lifecycle != null) - { - // Extract the value of "Environment" - var envObject = lifecycle[child] as JObject; - var env = envObject; - - if (env != null) - { - // Convert Environment JSON Object as a dictionnary - var keyValuePairs = env!.ToObject>(); - - foreach (var kvp in keyValuePairs!) - { - var iotEnvVariable = new IoTEdgeModuleEnvironmentVariable - { - Name = kvp.Key, - Value = kvp.Value - }; - - environmentVariables.Add(iotEnvVariable); - } - } - } - } - return environmentVariables; - } - - public Task> GetModelSystemModule(string modelId) - { - throw new NotImplementedException(); - } - - public Task> GetConfigRouteList(string modelId) - { - throw new NotImplementedException(); - } - - } -} +// 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.Services.AWS +{ + using System.Collections.Generic; + using System.Net; + using System.Text; + using System.Threading.Tasks; + using Amazon.GreengrassV2; + using Amazon.GreengrassV2.Model; + using Amazon.IoT; + using Amazon.IoT.Model; + using AutoMapper; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10; + using Configuration = Microsoft.Azure.Devices.Configuration; + + public class AwsConfigService : IConfigService + { + private readonly IAmazonGreengrassV2 greengrass; + private readonly IAmazonIoT iotClient; + + private readonly IUnitOfWork unitOfWork; + private readonly IEdgeDeviceModelRepository edgeModelRepository; + private readonly ConfigHandler config; + private readonly IMapper mapper; + + public AwsConfigService( + IAmazonGreengrassV2 greengrass, + IAmazonIoT iot, + IMapper mapper, + IUnitOfWork unitOfWork, + IEdgeDeviceModelRepository edgeModelRepository, + ConfigHandler config) + { + this.greengrass = greengrass; + this.iotClient = iot; + this.mapper = mapper; + this.unitOfWork = unitOfWork; + this.edgeModelRepository = edgeModelRepository; + this.config = config; + } + + public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) + { + + var createDeploymentRequest = new CreateDeploymentRequest + { + DeploymentName = edgeModel?.Name, + Components = await CreateGreenGrassComponents(edgeModel!), + TargetArn = await GetThingGroupArn(edgeModel!) + }; + + var createDeploymentResponse = await this.greengrass.CreateDeploymentAsync(createDeploymentRequest); + + if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) + { + throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); + + } + else + { + var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId!); + if (edgeModelEntity == null) + { + throw new Domain.Exceptions.ResourceNotFoundException($"The edge model with id {edgeModel?.ModelId} not found"); + + } + else + { + edgeModel!.ExternalIdentifier = createDeploymentResponse.DeploymentId; + + _ = this.mapper.Map(edgeModel, edgeModelEntity); + + this.edgeModelRepository.Update(edgeModelEntity); + await this.unitOfWork.SaveAsync(); + } + + } + } + + private async Task GetThingGroupArn(IoTEdgeModel edgeModel) + { + await CreateThingTypeIfNotExists(edgeModel!.Name); + + var dynamicThingGroup = new DescribeThingGroupRequest + { + ThingGroupName = edgeModel?.Name + }; + + try + { + var existingThingGroupResponse = await this.iotClient.DescribeThingGroupAsync(dynamicThingGroup); + + return existingThingGroupResponse.ThingGroupArn; + } + catch (Amazon.IoT.Model.ResourceNotFoundException) + { + var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest + { + ThingGroupName = edgeModel!.Name, + QueryString = $"thingTypeName: {edgeModel!.Name}" + }); + + return createThingGroupResponse.ThingGroupArn; + } + } + + private async Task CreateThingTypeIfNotExists(string thingTypeName) + { + var existingThingType = new DescribeThingTypeRequest + { + ThingTypeName = thingTypeName + }; + + try + { + _ = await this.iotClient.DescribeThingTypeAsync(existingThingType); + } + catch (Amazon.IoT.Model.ResourceNotFoundException) + { + _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest + { + ThingTypeName = thingTypeName, + Tags = new List + { + new Tag + { + Key = "iotEdge", + Value = "True" + } + } + }); + } + } + + private async Task> CreateGreenGrassComponents(IoTEdgeModel edgeModel) + { + var components = new Dictionary(); + foreach (var component in edgeModel.EdgeModules) + { + try + { + _ = await this.greengrass.DescribeComponentAsync(new DescribeComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}" + }); + components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); + + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) + { + var componentVersion = new CreateComponentVersionRequest + { + InlineRecipe = new MemoryStream(Encoding.UTF8.GetBytes(component.ContainerCreateOptions)) + }; + var response = await greengrass.CreateComponentVersionAsync(componentVersion); + if (response.HttpStatusCode != HttpStatusCode.Created) + { + throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API."); + + } + components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); + } + } + + return components; + } + + // TODO: To delete if no more needed + //private static JObject JsonCreateComponent(IoTEdgeModule component) + //{ + + // var environmentVariableObject = new JObject(); + + // foreach (var env in component.EnvironmentVariables) + // { + // environmentVariableObject.Add(new JProperty(env.Name, env.Value)); + // } + + // var recipeJson =new JObject( + // new JProperty("RecipeFormatVersion", "2020-01-25"), + // new JProperty("ComponentName", component.ModuleName), + // new JProperty("ComponentVersion", component.Version), + // new JProperty("ComponentPublisher", "IotHub"), + // new JProperty("ComponentDependencies", + // new JObject( + // new JProperty("aws.greengrass.DockerApplicationManager", + // new JObject(new JProperty("VersionRequirement", "~2.0.0"))), + // new JProperty("aws.greengrass.TokenExchangeService", + // new JObject(new JProperty("VersionRequirement", "~2.0.0"))) + // ) + // ), + // new JProperty("Manifests", + // new JArray( + // new JObject( + // new JProperty("Platform", + // new JObject(new JProperty("os", "linux"))), + // new JProperty("Lifecycle", + // new JObject(new JProperty("Run", $"docker run {component.ImageURI}"), + // new JProperty("Environment",environmentVariableObject))), + // new JProperty("Artifacts", + // new JArray( + // new JObject(new JProperty("URI", $"docker:{component.ImageURI}")) + // ) + // ) + // ) + // ) + // ) + // ); + + // return recipeJson; + //} + + //AWS Not implemented methods + + public Task> GetIoTEdgeConfigurations() + { + throw new NotImplementedException(); + } + + public Task> GetDevicesConfigurations() + { + throw new NotImplementedException(); + } + + public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) + { + throw new NotImplementedException(); + } + + public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) + { + throw new NotImplementedException(); + } + + public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) + { + throw new NotImplementedException(); + } + + public Task GetConfigItem(string id) + { + throw new NotImplementedException(); + } + + public async Task DeleteConfiguration(string modelId) + { + var modules = await GetConfigModuleList(modelId); + foreach (var module in modules) + { + var deletedComponentResponse = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" + }); + + if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) + { + throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); + + } + } + + var cancelDeploymentResponse = await this.greengrass.CancelDeploymentAsync(new CancelDeploymentRequest + { + DeploymentId = modelId + }); + if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) + { + throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); + + } + else + { + var deleteDeploymentResponse = await this.greengrass.DeleteDeploymentAsync(new DeleteDeploymentRequest + { + DeploymentId = modelId + }); + + if (deleteDeploymentResponse.HttpStatusCode != HttpStatusCode.NoContent) + { + throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API."); + } + } + + } + + public Task GetFailedDeploymentsCount() + { + throw new NotImplementedException(); + } + + public async Task> GetConfigModuleList(string modelId) + { + + var moduleList = new List(); + + var getDeployement = new GetDeploymentRequest + { + DeploymentId = modelId, + }; + try + { + var response = await this.greengrass.GetDeploymentAsync(getDeployement); + + foreach (var compoenent in response.Components) + { + var responseComponent = await this.greengras.GetComponentAsync(new GetComponentRequest + { + var responseComponent = await this.greengras.GetComponentAsync(new GetComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", + RecipeOutputFormat = RecipeOutputFormat.JSON + }); + + // Read the Recipe which is in JSON Format + using var reader = new StreamReader(responseComponent.Recipe); + var recipeJsonString = reader.ReadToEnd(); + + // Extract the imageUri from the 'Run' JSON object + var uriImage = retreiveImageUri("Lifecycle", "Run", recipeJsonString); + // Extract the environment Variables from the 'Environment' JSON object + var env = retreiveEnvVariableAttr("Lifecycle", "Environment", recipeJsonString); + + var iotEdgeModule = new IoTEdgeModule + { + ModuleName = compoenent.Key, + ImageURI = uriImage, + EnvironmentVariables = env, + Version = compoenent.Value.ComponentVersion + }; + + } + + + } + return moduleList; + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) + { + throw new InternalServerErrorException("The deployment is not found"); + + } + } + + // TODO: Delete if no more needed + //private static string retreiveImageUri(string parent, string child, string recipeJsonString) + //{ + // var uriImage = ""; + // // Parse the string as a JSON object + // var recipeJsonObject = JObject.Parse(recipeJsonString); + + // // Extract the "Manifests" array + // var jArray = recipeJsonObject["Manifests"] as JArray; + // var manifests = jArray; + + // if (manifests != null && manifests.Count > 0) + // { + // // Get the first manifest in the array + // var firstManifest = manifests[0] as JObject; + + // // Extract the "Lifecycle" object + // var jObject = firstManifest?[parent] as JObject; + // var lifecycle = jObject; + + // if (lifecycle != null) + // { + // // Extract the value of "Run" + // var runValue = lifecycle[child]?.ToString(); + + if (lifecycle != null) + { + // Extract the value of "Run" + var runValue = lifecycle[child]?.ToString(); + + // Search the index of the 1st whitespace + var firstSpaceIndex = runValue.IndexOf(' '); + + if (firstSpaceIndex != -1) + { + // // Search the index of the 2nd whitespace + var secondSpaceIndex = runValue.IndexOf(' ', firstSpaceIndex + 1); + + if (secondSpaceIndex != -1) + { + // Extract the URI iamge + uriImage = runValue[(secondSpaceIndex + 1)..]; + } + + } + } + } + + //private static List retreiveEnvVariableAttr(string parent, string child, string recipeJsonString) + //{ + + // // Parse the string as a JSON object + // var recipeJsonObject = JObject.Parse(recipeJsonString); + + // var environmentVariables = new List(); + + // // Extract the "Manifests" array + // var jArray = recipeJsonObject["Manifests"] as JArray; + // var manifests = jArray; + + // if (manifests != null && manifests.Count > 0) + // { + // // Get the first manifest in the array + // var firstManifest = manifests[0] as JObject; + + // // Extract the "Lifecycle" object + // var jObject = firstManifest?[parent] as JObject; + // var lifecycle = jObject; + + // if (lifecycle != null) + // { + // // Extract the value of "Environment" + // var env = lifecycle?[child] as JObject; + + if (lifecycle != null) + { + // Extract the value of "Environment" + var env = lifecycle?[child] as JObject; + + // Convert Environment JSON Object as a dictionnary + var keyValuePairs = env!.ToObject>(); + + foreach (var kvp in keyValuePairs!) + { + var iotEnvVariable = new IoTEdgeModuleEnvironmentVariable + { + Name = kvp.Key, + Value = kvp.Value + }; + + environmentVariables.Add(iotEnvVariable); + } + } + } + return environmentVariables; + } + + public Task> GetModelSystemModule(string modelId) + { + throw new NotImplementedException(); + } + + public Task> GetConfigRouteList(string modelId) + { + throw new NotImplementedException(); + } + + public async Task> GetPublicEdgeModules() + { + var publicComponents = await this.greengrass.ListComponentsAsync(new ListComponentsRequest + { + Scope = ComponentVisibilityScope.PUBLIC + }); + + return publicComponents.Components.Select(c => new IoTEdgeModule + { + Id = c.Arn, + ModuleName = c.ComponentName, + Version = c.LatestVersion.ComponentVersion + }); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs index ea68eda6a..6f2e5bcb2 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs @@ -343,7 +343,11 @@ public Task UpdateEdgeModelAvatar(string edgeModelId, IFormFile file) public Task DeleteEdgeModelAvatar(string edgeModelId) { return Task.Run(() => this.deviceModelImageManager.DeleteDeviceModelImageAsync(edgeModelId)); + } + + public Task> GetPublicEdgeModules() + { + return this.configService.GetPublicEdgeModules(); } - } } diff --git a/src/AzureIoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs b/src/AzureIoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs index c57b7b995..22778508f 100644 --- a/src/AzureIoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs +++ b/src/AzureIoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs @@ -108,5 +108,17 @@ public virtual async Task DeleteAvatar(string edgeModelId) await this.edgeModelService.DeleteEdgeModelAvatar(edgeModelId); return NoContent(); } + + /// + /// Get public edge modules + /// + /// Public edge modules + [HttpGet("public-modules", Name = "GET edge public modules")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public virtual async Task>> GetPublicEdgeModules() + { + return Ok(await this.edgeModelService.GetPublicEdgeModules()); + } } } diff --git a/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs b/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs index 95dbb2708..1dfde207a 100644 --- a/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs +++ b/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs @@ -349,6 +349,11 @@ public async Task GetFailedDeploymentsCount() { throw new InternalServerErrorException("Unable to get failed deployments count", ex); } - } + } + + public Task> GetPublicEdgeModules() + { + return Task.FromResult>(Array.Empty()); + } } } From c6fe3c2ef0be2ac575dec08e034ae052eff15cff Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 12:19:56 +0200 Subject: [PATCH 03/23] Add dialogs for aws json component + public components --- .../AwsGreengrassComponentDialog.razor | 133 ++++++++++++++++++ .../AwsGreengrassPublicComponents.razor | 53 +++++++ .../Enums/Context.cs | 11 ++ src/AzureIoTHub.Portal.Client/_Imports.razor | 1 + 4 files changed, 198 insertions(+) create mode 100644 src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor create mode 100644 src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor create mode 100644 src/AzureIoTHub.Portal.Client/Enums/Context.cs diff --git a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor new file mode 100644 index 000000000..038f131ad --- /dev/null +++ b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor @@ -0,0 +1,133 @@ +@using AzureIoTHub.Portal.Models.v10; +@using System.Text.Json; + + + + + + + + + + + Cancel + Submit + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public IoTEdgeModule Module { get; set; } = default!; + + [Parameter] + public Context Context { get; set; } = default!; + + private bool formIsValid; + private MudForm form = default!; + + private string currentModuleName = default!; + private string currentModuleVersion = default!; + private string jsonRecipe = default!; + + protected override void OnInitialized() + { + if (Context == Context.Create) + { + InitRecipe(); + } + else + { + jsonRecipe = Module.ContainerCreateOptions; + } + } + + private void InitRecipe() + { + // This is a sample recipe for a Greengrass component. It is not intended to be used in production. + jsonRecipe = @" +{ + ""RecipeFormatVersion"": ""2020-01-25"", + ""ComponentName"": ""com.example.HelloWorld"", + ""ComponentVersion"": ""1.0.0"", + ""ComponentDescription"": ""My first Greengrass component."", + ""ComponentPublisher"": ""Me"", + ""ComponentConfiguration"": { + ""DefaultConfiguration"": { + ""Message"": ""world"" + } + }, + ""Manifests"": [ + { + ""Name"": ""Linux"", + ""Platform"": { + ""os"": ""linux"" + }, + ""Lifecycle"": { + ""Run"": ""python3 {artifacts:path}/hello_world.py '{configuration:/Message}'"" + }, + ""Artifacts"": [ + { + ""Uri"": ""s3://EXAMPLE_BUCKET/artifacts/com.example.HelloWorld/1.0.0/hello_world.py"" + } + ] + } + ] +} +"; + } + + private IEnumerable ValidateJsonRecipe(string json) + { + var jsonProperties = JsonSerializer.Deserialize>(json) ?? new Dictionary(); + + if (!jsonProperties.ContainsKey("ComponentName")) + { + currentModuleName = string.Empty; + yield return "ComponentName is missing"; + } + else if (string.IsNullOrEmpty(jsonProperties["ComponentName"].ToString())) + { + currentModuleName = string.Empty; + yield return "ComponentName is empty"; + } + else + { + currentModuleName = jsonProperties["ComponentName"].ToString(); + } + + if (!jsonProperties.ContainsKey("ComponentVersion")) + { + currentModuleVersion = string.Empty; + yield return "ComponentVersion is missing"; + } + else if (string.IsNullOrEmpty(jsonProperties["ComponentVersion"].ToString())) + { + currentModuleVersion = string.Empty; + yield return "ComponentVersion is empty"; + } + else + { + currentModuleVersion = jsonProperties["ComponentVersion"].ToString(); + } + } + + void Cancel() => MudDialog.Cancel(); + + public async Task Submit() + { + await form.Validate(); + if (!form.IsValid) return; + + Module.ModuleName = currentModuleName; + Module.Version = currentModuleVersion; + // ImageURI is required, but not used for Greengrass components + Module.ImageURI = "example.com"; + Module.ContainerCreateOptions = jsonRecipe; + + MudDialog.Close(DialogResult.Ok(true)); + } + +} diff --git a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor new file mode 100644 index 000000000..bd0697111 --- /dev/null +++ b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor @@ -0,0 +1,53 @@ +@using AzureIoTHub.Portal.Models.v10; + +@inject IEdgeModelClientService EdgeModelClientService + + + + + + Name + Version + + + @context.ModuleName + @context.Version + + + + + + + + Cancel + Submit + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public List EdgeModules { get; set; } = default!; + + private MudTable _table; + private List publicComponents = new(); + private HashSet selectedPublicComponents = new HashSet(); + + protected async override Task OnInitializedAsync() + { + selectedPublicComponents = new HashSet(EdgeModules.Where(m => !string.IsNullOrEmpty(m.Id))); + publicComponents = await EdgeModelClientService.GetPublicEdgeModules(); + } + + private void Cancel() => MudDialog.Cancel(); + + private void Submit() + { + EdgeModules.AddRange(selectedPublicComponents.ToList()); + + MudDialog.Close(DialogResult.Ok(true)); + } + +} diff --git a/src/AzureIoTHub.Portal.Client/Enums/Context.cs b/src/AzureIoTHub.Portal.Client/Enums/Context.cs new file mode 100644 index 000000000..d14595620 --- /dev/null +++ b/src/AzureIoTHub.Portal.Client/Enums/Context.cs @@ -0,0 +1,11 @@ +// 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.Client.Enums +{ + public enum Context + { + Create, + Edit + } +} diff --git a/src/AzureIoTHub.Portal.Client/_Imports.razor b/src/AzureIoTHub.Portal.Client/_Imports.razor index 3ff9ce7b0..5c18999e1 100644 --- a/src/AzureIoTHub.Portal.Client/_Imports.razor +++ b/src/AzureIoTHub.Portal.Client/_Imports.razor @@ -27,5 +27,6 @@ @using AzureIoTHub.Portal.Client.Components.Devices.LoRaWAN @using AzureIoTHub.Portal.Client.Components.EdgeDevices @using AzureIoTHub.Portal.Client.Components.EdgeModels +@using AzureIoTHub.Portal.Client.Dialogs.EdgeModels @using MudBlazor @using ChartJs.Blazor; From 2f7875afd576b5213cb053d418a43db60be55af8 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 12:21:02 +0200 Subject: [PATCH 04/23] Update create and edit edge model pages to handle aws greengrass components --- .../EdgeModels/CreateEdgeModelsPage.razor | 185 ++++++++++++------ .../EdgeModels/EdgeModelDetailPage.razor | 160 +++++++++------ 2 files changed, 222 insertions(+), 123 deletions(-) diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor index 7226764f4..06e7df05a 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor @@ -104,51 +104,48 @@ - } - - - - Modules - - - - - - - - - - - Module name - Image URI - See detail - Delete - - - - - - - - - - Detail - - - - - - - Add new module - - - - - - - - @if (Portal.CloudProvider.Equals(CloudProviders.Azure)) - { + + + + Modules + + + + + + + + + + + Module name + Image URI + See detail + Delete + + + + + + + + + + Detail + + + + + + + Add new module + + + + + + + @@ -196,6 +193,47 @@ + } + @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) + { + + + + Modules + + + + + + + + + + Module name + See detail + Delete + + + + @moduleContext.ModuleName + + + Detail + + + + + + + Add new module + Add public modules + + + + + + + } @@ -217,7 +255,7 @@ -@code { +@code { [CascadingParameter] public Error Error { get; set; } = default!; @@ -267,28 +305,49 @@ } private void AddModule() - { - edgeModules.Add(new IoTEdgeModule()); - } - + { + var edgeModule = new IoTEdgeModule(); + edgeModules.Add(edgeModule); + + if (Portal.CloudProvider.Equals(CloudProviders.AWS)) { + ShowAddEdgeModuleDialog(edgeModule); + } + } + + private async Task ShowAddEdgePublicModulesDialog() + { + var parameters = new DialogParameters(); + parameters.Add("EdgeModules", edgeModules); + + DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; + + await DialogService.Show(string.Empty, parameters, options).Result; + + await InvokeAsync(StateHasChanged); + } + private async Task ShowAddEdgeModuleDialog(IoTEdgeModule module) { var parameters = new DialogParameters(); parameters.Add("module", module); - DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; - - if (!string.IsNullOrWhiteSpace(module.ModuleName)) - { - var result = await DialogService.Show(module.ModuleName, parameters, options).Result; - - if (result.Canceled) - { - return; - } - } - } - + DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; + + if (Portal.CloudProvider.Equals(CloudProviders.Azure)) + { + if (!string.IsNullOrWhiteSpace(module.ModuleName)) + { + await DialogService.Show(module.ModuleName, parameters, options).Result; + } + } + else + { + if (string.IsNullOrEmpty(module.ContainerCreateOptions)) parameters.Add("Context", Context.Create); + + await DialogService.Show(module.ModuleName, parameters, options).Result; + } + } + private async Task ShowSystemModuleDetail(EdgeModelSystemModule systemModule) { var parameters = new DialogParameters(); diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor index d5538a896..f1763a2ac 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor @@ -110,52 +110,49 @@ - } - - - - Modules - - - - - - - - - - - Module name - Image URI - See detail - Delete - - - - - - - - - - Detail - - - - - - - Add new module - - - - - - - - @if (Portal.CloudProvider.Equals(CloudProviders.Azure)) - { + + + + Modules + + + + + + + + + + + Module name + Image URI + See detail + Delete + + + + + + + + + + Detail + + + + + + + Add new module + + + + + + + @@ -204,7 +201,46 @@ } - + @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) + { + + + + Modules + + + + + + + + + + Module name + See detail + Delete + + + + @moduleContext.ModuleName + + + Detail + + + + + + + Add new module + + + + + + + + } @@ -363,20 +399,24 @@ private async Task ShowEditEdgeModuleDialog(IoTEdgeModule module) { - var parameters = new DialogParameters(); - parameters.Add("module", module); - - DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; - - if (!string.IsNullOrWhiteSpace(module.ModuleName)) - { - var result = await DialogService.Show(module.ModuleName, parameters, options).Result; - - if (result.Canceled) - { - return; - } - } + var parameters = new DialogParameters(); + parameters.Add("module", module); + + DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; + + if (Portal.CloudProvider.Equals(CloudProviders.Azure)) + { + if (!string.IsNullOrWhiteSpace(module.ModuleName)) + { + await DialogService.Show(module.ModuleName, parameters, options).Result; + } + } + else + { + parameters.Add("Context", Context.Edit); + + await DialogService.Show(module.ModuleName, parameters, options).Result; + } } private void DeleteModule(IoTEdgeModule module) From f5b7ba16d339bba18eff71fef48a6e1aeb737373 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 19:35:00 +0200 Subject: [PATCH 05/23] Update GetPublicEdgeModules to load all public greengrass components + fix loading public component --- .../Services/AWS/AwsConfigService.cs | 926 +++++++++--------- 1 file changed, 470 insertions(+), 456 deletions(-) diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs index 4aa17e5fc..44a943d92 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs @@ -1,473 +1,487 @@ -// 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.Services.AWS -{ - using System.Collections.Generic; - using System.Net; - using System.Text; - using System.Threading.Tasks; - using Amazon.GreengrassV2; - using Amazon.GreengrassV2.Model; - using Amazon.IoT; - using Amazon.IoT.Model; - using AutoMapper; - using AzureIoTHub.Portal.Application.Services; - using AzureIoTHub.Portal.Domain; - using AzureIoTHub.Portal.Domain.Exceptions; - using AzureIoTHub.Portal.Domain.Repositories; - using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Shared.Models.v10; - using Configuration = Microsoft.Azure.Devices.Configuration; - - public class AwsConfigService : IConfigService - { - private readonly IAmazonGreengrassV2 greengrass; - private readonly IAmazonIoT iotClient; - - private readonly IUnitOfWork unitOfWork; - private readonly IEdgeDeviceModelRepository edgeModelRepository; - private readonly ConfigHandler config; - private readonly IMapper mapper; - - public AwsConfigService( - IAmazonGreengrassV2 greengrass, - IAmazonIoT iot, - IMapper mapper, - IUnitOfWork unitOfWork, - IEdgeDeviceModelRepository edgeModelRepository, - ConfigHandler config) - { - this.greengrass = greengrass; - this.iotClient = iot; - this.mapper = mapper; - this.unitOfWork = unitOfWork; - this.edgeModelRepository = edgeModelRepository; - this.config = config; - } - - public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) - { - - var createDeploymentRequest = new CreateDeploymentRequest - { - DeploymentName = edgeModel?.Name, - Components = await CreateGreenGrassComponents(edgeModel!), - TargetArn = await GetThingGroupArn(edgeModel!) - }; - - var createDeploymentResponse = await this.greengrass.CreateDeploymentAsync(createDeploymentRequest); - - if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) - { - throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); - - } - else - { - var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId!); - if (edgeModelEntity == null) - { - throw new Domain.Exceptions.ResourceNotFoundException($"The edge model with id {edgeModel?.ModelId} not found"); - - } - else - { - edgeModel!.ExternalIdentifier = createDeploymentResponse.DeploymentId; - - _ = this.mapper.Map(edgeModel, edgeModelEntity); - - this.edgeModelRepository.Update(edgeModelEntity); - await this.unitOfWork.SaveAsync(); - } - - } - } - - private async Task GetThingGroupArn(IoTEdgeModel edgeModel) - { - await CreateThingTypeIfNotExists(edgeModel!.Name); - - var dynamicThingGroup = new DescribeThingGroupRequest - { - ThingGroupName = edgeModel?.Name - }; - - try - { - var existingThingGroupResponse = await this.iotClient.DescribeThingGroupAsync(dynamicThingGroup); - - return existingThingGroupResponse.ThingGroupArn; - } - catch (Amazon.IoT.Model.ResourceNotFoundException) - { - var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest - { - ThingGroupName = edgeModel!.Name, - QueryString = $"thingTypeName: {edgeModel!.Name}" - }); - - return createThingGroupResponse.ThingGroupArn; - } - } - - private async Task CreateThingTypeIfNotExists(string thingTypeName) - { - var existingThingType = new DescribeThingTypeRequest - { - ThingTypeName = thingTypeName - }; - - try - { - _ = await this.iotClient.DescribeThingTypeAsync(existingThingType); - } - catch (Amazon.IoT.Model.ResourceNotFoundException) - { - _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest +// 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.Services.AWS +{ + using System.Collections.Generic; + using System.Net; + using System.Text; + using System.Threading.Tasks; + using Amazon.GreengrassV2; + using Amazon.GreengrassV2.Model; + using Amazon.IoT; + using Amazon.IoT.Model; + using AutoMapper; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10; + using Configuration = Microsoft.Azure.Devices.Configuration; + + public class AwsConfigService : IConfigService + { + private readonly IAmazonGreengrassV2 greengrass; + private readonly IAmazonIoT iotClient; + + private readonly IUnitOfWork unitOfWork; + private readonly IEdgeDeviceModelRepository edgeModelRepository; + private readonly ConfigHandler config; + private readonly IMapper mapper; + + public AwsConfigService( + IAmazonGreengrassV2 greengrass, + IAmazonIoT iot, + IMapper mapper, + IUnitOfWork unitOfWork, + IEdgeDeviceModelRepository edgeModelRepository, + ConfigHandler config) + { + this.greengrass = greengrass; + this.iotClient = iot; + this.mapper = mapper; + this.unitOfWork = unitOfWork; + this.edgeModelRepository = edgeModelRepository; + this.config = config; + } + + public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) + { + + var createDeploymentRequest = new CreateDeploymentRequest + { + DeploymentName = edgeModel?.Name, + Components = await CreateGreenGrassComponents(edgeModel!), + TargetArn = await GetThingGroupArn(edgeModel!) + }; + + var createDeploymentResponse = await this.greengrass.CreateDeploymentAsync(createDeploymentRequest); + + if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) + { + throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); + + } + else + { + var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId!); + if (edgeModelEntity == null) + { + throw new Domain.Exceptions.ResourceNotFoundException($"The edge model with id {edgeModel?.ModelId} not found"); + + } + else + { + edgeModel!.ExternalIdentifier = createDeploymentResponse.DeploymentId; + + _ = this.mapper.Map(edgeModel, edgeModelEntity); + + this.edgeModelRepository.Update(edgeModelEntity); + await this.unitOfWork.SaveAsync(); + } + + } + } + + private async Task GetThingGroupArn(IoTEdgeModel edgeModel) + { + await CreateThingTypeIfNotExists(edgeModel!.Name); + + var dynamicThingGroup = new DescribeThingGroupRequest + { + ThingGroupName = edgeModel?.Name + }; + + try + { + var existingThingGroupResponse = await this.iotClient.DescribeThingGroupAsync(dynamicThingGroup); + + return existingThingGroupResponse.ThingGroupArn; + } + catch (Amazon.IoT.Model.ResourceNotFoundException) + { + var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest + { + ThingGroupName = edgeModel!.Name, + QueryString = $"thingTypeName: {edgeModel!.Name}" + }); + + return createThingGroupResponse.ThingGroupArn; + } + } + + private async Task CreateThingTypeIfNotExists(string thingTypeName) + { + var existingThingType = new DescribeThingTypeRequest + { + ThingTypeName = thingTypeName + }; + + try + { + _ = await this.iotClient.DescribeThingTypeAsync(existingThingType); + } + catch (Amazon.IoT.Model.ResourceNotFoundException) + { + _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest + { + ThingTypeName = thingTypeName, + Tags = new List + { + new Tag + { + Key = "iotEdge", + Value = "True" + } + } + }); + } + } + + private async Task> CreateGreenGrassComponents(IoTEdgeModel edgeModel) + { + var components = new Dictionary(); + foreach (var component in edgeModel.EdgeModules) + { + try { - ThingTypeName = thingTypeName, - Tags = new List - { - new Tag - { - Key = "iotEdge", - Value = "True" - } - } - }); + var componentArn = !string.IsNullOrEmpty(component.Id) ? + $"{component.Id}:versions:{component.Version}" : // Public greengrass component + $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}"; // Private greengrass component + + _ = await this.greengrass.DescribeComponentAsync(new DescribeComponentRequest + { + Arn = componentArn + }); + components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); + + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) + { + var componentVersion = new CreateComponentVersionRequest + { + InlineRecipe = new MemoryStream(Encoding.UTF8.GetBytes(component.ContainerCreateOptions)) + }; + var response = await greengrass.CreateComponentVersionAsync(componentVersion); + if (response.HttpStatusCode != HttpStatusCode.Created) + { + throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API."); + + } + components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); + } } - } - - private async Task> CreateGreenGrassComponents(IoTEdgeModel edgeModel) - { - var components = new Dictionary(); - foreach (var component in edgeModel.EdgeModules) - { - try - { - _ = await this.greengrass.DescribeComponentAsync(new DescribeComponentRequest - { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}" - }); - components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); - - } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) - { - var componentVersion = new CreateComponentVersionRequest - { - InlineRecipe = new MemoryStream(Encoding.UTF8.GetBytes(component.ContainerCreateOptions)) - }; - var response = await greengrass.CreateComponentVersionAsync(componentVersion); - if (response.HttpStatusCode != HttpStatusCode.Created) + + return components; + } + + // TODO: To delete if no more needed + //private static JObject JsonCreateComponent(IoTEdgeModule component) + //{ + + // var environmentVariableObject = new JObject(); + + // foreach (var env in component.EnvironmentVariables) + // { + // environmentVariableObject.Add(new JProperty(env.Name, env.Value)); + // } + + // var recipeJson =new JObject( + // new JProperty("RecipeFormatVersion", "2020-01-25"), + // new JProperty("ComponentName", component.ModuleName), + // new JProperty("ComponentVersion", component.Version), + // new JProperty("ComponentPublisher", "IotHub"), + // new JProperty("ComponentDependencies", + // new JObject( + // new JProperty("aws.greengrass.DockerApplicationManager", + // new JObject(new JProperty("VersionRequirement", "~2.0.0"))), + // new JProperty("aws.greengrass.TokenExchangeService", + // new JObject(new JProperty("VersionRequirement", "~2.0.0"))) + // ) + // ), + // new JProperty("Manifests", + // new JArray( + // new JObject( + // new JProperty("Platform", + // new JObject(new JProperty("os", "linux"))), + // new JProperty("Lifecycle", + // new JObject(new JProperty("Run", $"docker run {component.ImageURI}"), + // new JProperty("Environment",environmentVariableObject))), + // new JProperty("Artifacts", + // new JArray( + // new JObject(new JProperty("URI", $"docker:{component.ImageURI}")) + // ) + // ) + // ) + // ) + // ) + // ); + + // return recipeJson; + //} + + //AWS Not implemented methods + + public Task> GetIoTEdgeConfigurations() + { + throw new NotImplementedException(); + } + + public Task> GetDevicesConfigurations() + { + throw new NotImplementedException(); + } + + public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) + { + throw new NotImplementedException(); + } + + public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) + { + throw new NotImplementedException(); + } + + public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) + { + throw new NotImplementedException(); + } + + public Task GetConfigItem(string id) + { + throw new NotImplementedException(); + } + + public async Task DeleteConfiguration(string modelId) + { + var modules = await GetConfigModuleList(modelId); + foreach (var module in modules) + { + var deletedComponentResponse = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" + }); + + if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) + { + throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); + + } + } + + var cancelDeploymentResponse = await this.greengrass.CancelDeploymentAsync(new CancelDeploymentRequest + { + DeploymentId = modelId + }); + if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) + { + throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); + + } + else + { + var deleteDeploymentResponse = await this.greengrass.DeleteDeploymentAsync(new DeleteDeploymentRequest + { + DeploymentId = modelId + }); + + if (deleteDeploymentResponse.HttpStatusCode != HttpStatusCode.NoContent) + { + throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API."); + } + } + + } + + public Task GetFailedDeploymentsCount() + { + throw new NotImplementedException(); + } + + public async Task> GetConfigModuleList(string modelId) + { + + var moduleList = new List(); + + var getDeployement = new GetDeploymentRequest + { + DeploymentId = modelId, + }; + try + { + var response = await this.greengrass.GetDeploymentAsync(getDeployement); + + foreach (var compoenent in response.Components) + { + var responseComponent = await this.greengrass.GetComponentAsync(new GetComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", + RecipeOutputFormat = RecipeOutputFormat.JSON + }); + + // Read the Recipe which is in JSON Format + using var reader = new StreamReader(responseComponent.Recipe); + var recipeJsonString = reader.ReadToEnd(); + + var iotEdgeModule = new IoTEdgeModule { - throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API."); - - } - components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); - } - } - - return components; - } - - // TODO: To delete if no more needed - //private static JObject JsonCreateComponent(IoTEdgeModule component) - //{ - - // var environmentVariableObject = new JObject(); - - // foreach (var env in component.EnvironmentVariables) - // { - // environmentVariableObject.Add(new JProperty(env.Name, env.Value)); - // } - - // var recipeJson =new JObject( - // new JProperty("RecipeFormatVersion", "2020-01-25"), - // new JProperty("ComponentName", component.ModuleName), - // new JProperty("ComponentVersion", component.Version), - // new JProperty("ComponentPublisher", "IotHub"), - // new JProperty("ComponentDependencies", - // new JObject( - // new JProperty("aws.greengrass.DockerApplicationManager", - // new JObject(new JProperty("VersionRequirement", "~2.0.0"))), - // new JProperty("aws.greengrass.TokenExchangeService", - // new JObject(new JProperty("VersionRequirement", "~2.0.0"))) - // ) - // ), - // new JProperty("Manifests", - // new JArray( - // new JObject( - // new JProperty("Platform", - // new JObject(new JProperty("os", "linux"))), - // new JProperty("Lifecycle", - // new JObject(new JProperty("Run", $"docker run {component.ImageURI}"), - // new JProperty("Environment",environmentVariableObject))), - // new JProperty("Artifacts", - // new JArray( - // new JObject(new JProperty("URI", $"docker:{component.ImageURI}")) - // ) - // ) - // ) - // ) - // ) - // ); - - // return recipeJson; - //} - - //AWS Not implemented methods - - public Task> GetIoTEdgeConfigurations() - { - throw new NotImplementedException(); - } - - public Task> GetDevicesConfigurations() - { - throw new NotImplementedException(); - } - - public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) - { - throw new NotImplementedException(); - } - - public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) - { - throw new NotImplementedException(); + Id = componentId, + ModuleName = compoenent.Key, + Version = compoenent.Value.ComponentVersion, + ContainerCreateOptions = recipeJsonString, + // ImageURI is required, but not used for Greengrass components + ImageURI = "example.com" + }; + + } + + + } + return moduleList; + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) + { + throw new InternalServerErrorException("The deployment is not found"); + + } + } + + // TODO: Delete if no more needed + //private static string retreiveImageUri(string parent, string child, string recipeJsonString) + //{ + // var uriImage = ""; + // // Parse the string as a JSON object + // var recipeJsonObject = JObject.Parse(recipeJsonString); + + // // Extract the "Manifests" array + // var jArray = recipeJsonObject["Manifests"] as JArray; + // var manifests = jArray; + + // if (manifests != null && manifests.Count > 0) + // { + // // Get the first manifest in the array + // var firstManifest = manifests[0] as JObject; + + // // Extract the "Lifecycle" object + // var jObject = firstManifest?[parent] as JObject; + // var lifecycle = jObject; + + // if (lifecycle != null) + // { + // // Extract the value of "Run" + // var runValue = lifecycle[child]?.ToString(); + + if (lifecycle != null) + { + // Extract the value of "Run" + var runValue = lifecycle[child]?.ToString(); + + // Search the index of the 1st whitespace + var firstSpaceIndex = runValue.IndexOf(' '); + + if (firstSpaceIndex != -1) + { + // // Search the index of the 2nd whitespace + var secondSpaceIndex = runValue.IndexOf(' ', firstSpaceIndex + 1); + + if (secondSpaceIndex != -1) + { + // Extract the URI iamge + uriImage = runValue[(secondSpaceIndex + 1)..]; + } + + } + } + } + + //private static List retreiveEnvVariableAttr(string parent, string child, string recipeJsonString) + //{ + + // // Parse the string as a JSON object + // var recipeJsonObject = JObject.Parse(recipeJsonString); + + // var environmentVariables = new List(); + + // // Extract the "Manifests" array + // var jArray = recipeJsonObject["Manifests"] as JArray; + // var manifests = jArray; + + // if (manifests != null && manifests.Count > 0) + // { + // // Get the first manifest in the array + // var firstManifest = manifests[0] as JObject; + + // // Extract the "Lifecycle" object + // var jObject = firstManifest?[parent] as JObject; + // var lifecycle = jObject; + + // if (lifecycle != null) + // { + // // Extract the value of "Environment" + // var env = lifecycle?[child] as JObject; + + if (lifecycle != null) + { + // Extract the value of "Environment" + var env = lifecycle?[child] as JObject; + + // Convert Environment JSON Object as a dictionnary + var keyValuePairs = env!.ToObject>(); + + foreach (var kvp in keyValuePairs!) + { + var iotEnvVariable = new IoTEdgeModuleEnvironmentVariable + { + Name = kvp.Key, + Value = kvp.Value + }; + + environmentVariables.Add(iotEnvVariable); + } + } + } + return environmentVariables; + } + + public Task> GetModelSystemModule(string modelId) + { + throw new NotImplementedException(); + } + + public Task> GetConfigRouteList(string modelId) + { + throw new NotImplementedException(); } - public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) + public async Task> GetPublicEdgeModules() { - throw new NotImplementedException(); - } + var publicComponents = new List(); - public Task GetConfigItem(string id) - { - throw new NotImplementedException(); - } + var nextToken = string.Empty; - public async Task DeleteConfiguration(string modelId) - { - var modules = await GetConfigModuleList(modelId); - foreach (var module in modules) + do { - var deletedComponentResponse = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest + var response = await this.greengrass.ListComponentsAsync(new ListComponentsRequest { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" + Scope = ComponentVisibilityScope.PUBLIC, + NextToken = nextToken }); - if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) - { - throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); + publicComponents.AddRange(response.Components); - } + nextToken = response.NextToken; } + while (!string.IsNullOrEmpty(nextToken)); - var cancelDeploymentResponse = await this.greengrass.CancelDeploymentAsync(new CancelDeploymentRequest + return publicComponents.Select(c => new IoTEdgeModule { - DeploymentId = modelId + Id = c.Arn, + ModuleName = c.ComponentName, + Version = c.LatestVersion.ComponentVersion, + // ImageURI is required, but not used for Greengrass components + ImageURI = "example.com" }); - if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) - { - throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); - - } - else - { - var deleteDeploymentResponse = await this.greengrass.DeleteDeploymentAsync(new DeleteDeploymentRequest - { - DeploymentId = modelId - }); - - if (deleteDeploymentResponse.HttpStatusCode != HttpStatusCode.NoContent) - { - throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API."); - } - } - - } - - public Task GetFailedDeploymentsCount() - { - throw new NotImplementedException(); - } - - public async Task> GetConfigModuleList(string modelId) - { - - var moduleList = new List(); - - var getDeployement = new GetDeploymentRequest - { - DeploymentId = modelId, - }; - try - { - var response = await this.greengrass.GetDeploymentAsync(getDeployement); - - foreach (var compoenent in response.Components) - { - var responseComponent = await this.greengras.GetComponentAsync(new GetComponentRequest - { - var responseComponent = await this.greengras.GetComponentAsync(new GetComponentRequest - { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", - RecipeOutputFormat = RecipeOutputFormat.JSON - }); - - // Read the Recipe which is in JSON Format - using var reader = new StreamReader(responseComponent.Recipe); - var recipeJsonString = reader.ReadToEnd(); - - // Extract the imageUri from the 'Run' JSON object - var uriImage = retreiveImageUri("Lifecycle", "Run", recipeJsonString); - // Extract the environment Variables from the 'Environment' JSON object - var env = retreiveEnvVariableAttr("Lifecycle", "Environment", recipeJsonString); - - var iotEdgeModule = new IoTEdgeModule - { - ModuleName = compoenent.Key, - ImageURI = uriImage, - EnvironmentVariables = env, - Version = compoenent.Value.ComponentVersion - }; - - } - - - } - return moduleList; - } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) - { - throw new InternalServerErrorException("The deployment is not found"); - - } - } - - // TODO: Delete if no more needed - //private static string retreiveImageUri(string parent, string child, string recipeJsonString) - //{ - // var uriImage = ""; - // // Parse the string as a JSON object - // var recipeJsonObject = JObject.Parse(recipeJsonString); - - // // Extract the "Manifests" array - // var jArray = recipeJsonObject["Manifests"] as JArray; - // var manifests = jArray; - - // if (manifests != null && manifests.Count > 0) - // { - // // Get the first manifest in the array - // var firstManifest = manifests[0] as JObject; - - // // Extract the "Lifecycle" object - // var jObject = firstManifest?[parent] as JObject; - // var lifecycle = jObject; - - // if (lifecycle != null) - // { - // // Extract the value of "Run" - // var runValue = lifecycle[child]?.ToString(); - - if (lifecycle != null) - { - // Extract the value of "Run" - var runValue = lifecycle[child]?.ToString(); - - // Search the index of the 1st whitespace - var firstSpaceIndex = runValue.IndexOf(' '); - - if (firstSpaceIndex != -1) - { - // // Search the index of the 2nd whitespace - var secondSpaceIndex = runValue.IndexOf(' ', firstSpaceIndex + 1); - - if (secondSpaceIndex != -1) - { - // Extract the URI iamge - uriImage = runValue[(secondSpaceIndex + 1)..]; - } - - } - } - } - - //private static List retreiveEnvVariableAttr(string parent, string child, string recipeJsonString) - //{ - - // // Parse the string as a JSON object - // var recipeJsonObject = JObject.Parse(recipeJsonString); - - // var environmentVariables = new List(); - - // // Extract the "Manifests" array - // var jArray = recipeJsonObject["Manifests"] as JArray; - // var manifests = jArray; - - // if (manifests != null && manifests.Count > 0) - // { - // // Get the first manifest in the array - // var firstManifest = manifests[0] as JObject; - - // // Extract the "Lifecycle" object - // var jObject = firstManifest?[parent] as JObject; - // var lifecycle = jObject; - - // if (lifecycle != null) - // { - // // Extract the value of "Environment" - // var env = lifecycle?[child] as JObject; - - if (lifecycle != null) - { - // Extract the value of "Environment" - var env = lifecycle?[child] as JObject; - - // Convert Environment JSON Object as a dictionnary - var keyValuePairs = env!.ToObject>(); - - foreach (var kvp in keyValuePairs!) - { - var iotEnvVariable = new IoTEdgeModuleEnvironmentVariable - { - Name = kvp.Key, - Value = kvp.Value - }; - - environmentVariables.Add(iotEnvVariable); - } - } - } - return environmentVariables; - } - - public Task> GetModelSystemModule(string modelId) - { - throw new NotImplementedException(); - } - - public Task> GetConfigRouteList(string modelId) - { - throw new NotImplementedException(); - } - - public async Task> GetPublicEdgeModules() - { - var publicComponents = await this.greengrass.ListComponentsAsync(new ListComponentsRequest - { - Scope = ComponentVisibilityScope.PUBLIC - }); - - return publicComponents.Components.Select(c => new IoTEdgeModule - { - Id = c.Arn, - ModuleName = c.ComponentName, - Version = c.LatestVersion.ComponentVersion - }); - } - } -} + } + } +} From 1d692da7f33d7387e2fa63383220e286bd134ed2 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 19:38:10 +0200 Subject: [PATCH 06/23] Update aws greengrass dialogs --- .../AwsGreengrassComponentDialog.razor | 71 +++++++------------ .../AwsGreengrassPublicComponents.razor | 3 +- 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor index 038f131ad..073480b17 100644 --- a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor +++ b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor @@ -22,6 +22,9 @@ [Parameter] public IoTEdgeModule Module { get; set; } = default!; + [Parameter] + public List EdgeModules { get; set; } = default!; + [Parameter] public Context Context { get; set; } = default!; @@ -34,51 +37,12 @@ protected override void OnInitialized() { - if (Context == Context.Create) - { - InitRecipe(); - } - else + if (Context == Context.Edit) { jsonRecipe = Module.ContainerCreateOptions; } } - private void InitRecipe() - { - // This is a sample recipe for a Greengrass component. It is not intended to be used in production. - jsonRecipe = @" -{ - ""RecipeFormatVersion"": ""2020-01-25"", - ""ComponentName"": ""com.example.HelloWorld"", - ""ComponentVersion"": ""1.0.0"", - ""ComponentDescription"": ""My first Greengrass component."", - ""ComponentPublisher"": ""Me"", - ""ComponentConfiguration"": { - ""DefaultConfiguration"": { - ""Message"": ""world"" - } - }, - ""Manifests"": [ - { - ""Name"": ""Linux"", - ""Platform"": { - ""os"": ""linux"" - }, - ""Lifecycle"": { - ""Run"": ""python3 {artifacts:path}/hello_world.py '{configuration:/Message}'"" - }, - ""Artifacts"": [ - { - ""Uri"": ""s3://EXAMPLE_BUCKET/artifacts/com.example.HelloWorld/1.0.0/hello_world.py"" - } - ] - } - ] -} -"; - } - private IEnumerable ValidateJsonRecipe(string json) { var jsonProperties = JsonSerializer.Deserialize>(json) ?? new Dictionary(); @@ -95,6 +59,10 @@ } else { + if (Context == Context.Create && EdgeModules.Any(m => m.ModuleName.Equals(jsonProperties["ComponentName"].ToString()))) + { + yield return $"Component {jsonProperties["ComponentName"].ToString()} is already used"; + } currentModuleName = jsonProperties["ComponentName"].ToString(); } @@ -121,11 +89,24 @@ await form.Validate(); if (!form.IsValid) return; - Module.ModuleName = currentModuleName; - Module.Version = currentModuleVersion; - // ImageURI is required, but not used for Greengrass components - Module.ImageURI = "example.com"; - Module.ContainerCreateOptions = jsonRecipe; + if(Context == Context.Create) + { + EdgeModules.Add(new IoTEdgeModule + { + ModuleName = currentModuleName, + Version = currentModuleVersion, + ImageURI = "example.com", + ContainerCreateOptions = jsonRecipe + }); + } + else + { + Module.ModuleName = currentModuleName; + Module.Version = currentModuleVersion; + // ImageURI is required, but not used for Greengrass components + Module.ImageURI = "example.com"; + Module.ContainerCreateOptions = jsonRecipe; + } MudDialog.Close(DialogResult.Ok(true)); } diff --git a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor index bd0697111..7602c9a46 100644 --- a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor +++ b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor @@ -4,7 +4,7 @@ - Name @@ -31,7 +31,6 @@ [Parameter] public List EdgeModules { get; set; } = default!; - private MudTable _table; private List publicComponents = new(); private HashSet selectedPublicComponents = new HashSet(); From 5a77328147b7ed58e6181c728b0c1be5387804a0 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 19:39:10 +0200 Subject: [PATCH 07/23] Fi edge model create/edit pages to handle public and private edge modules (aws components) --- .../EdgeModels/CreateEdgeModelsPage.razor | 29 +++++++++------ .../EdgeModels/EdgeModelDetailPage.razor | 36 ++++++++++++++++--- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor index 06e7df05a..c4c6a959a 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor @@ -218,14 +218,14 @@ @moduleContext.ModuleName - Detail + Detail - Add new module + Add new module Add public modules @@ -306,12 +306,7 @@ private void AddModule() { - var edgeModule = new IoTEdgeModule(); - edgeModules.Add(edgeModule); - - if (Portal.CloudProvider.Equals(CloudProviders.AWS)) { - ShowAddEdgeModuleDialog(edgeModule); - } + edgeModules.Add(new IoTEdgeModule()); } private async Task ShowAddEdgePublicModulesDialog() @@ -329,7 +324,6 @@ private async Task ShowAddEdgeModuleDialog(IoTEdgeModule module) { var parameters = new DialogParameters(); - parameters.Add("module", module); DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; @@ -337,17 +331,32 @@ { if (!string.IsNullOrWhiteSpace(module.ModuleName)) { + + parameters.Add("module", module); await DialogService.Show(module.ModuleName, parameters, options).Result; } } else { - if (string.IsNullOrEmpty(module.ContainerCreateOptions)) parameters.Add("Context", Context.Create); + parameters.Add("Context", Context.Edit); + parameters.Add("Module", module); await DialogService.Show(module.ModuleName, parameters, options).Result; } } + private async Task ShowAddNewEdgeModuleDialog() + { + var parameters = new DialogParameters(); + + DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; + + parameters.Add("Context", Context.Create); + parameters.Add("EdgeModules", edgeModules); + + await DialogService.Show(string.Empty, parameters, options).Result; + } + private async Task ShowSystemModuleDetail(EdgeModelSystemModule systemModule) { var parameters = new DialogParameters(); diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor index f1763a2ac..4b2d7861e 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor @@ -225,14 +225,15 @@ @moduleContext.ModuleName - Detail + Detail - Add new module + Add new module + Add public modules @@ -394,9 +395,33 @@ private void AddModule() { - EdgeModel.EdgeModules.Add(new IoTEdgeModule()); - } - + EdgeModel.EdgeModules.Add(new IoTEdgeModule()); + } + + private async Task ShowAddNewEdgeModuleDialog() + { + var parameters = new DialogParameters(); + + DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; + + parameters.Add("Context", Context.Create); + parameters.Add("EdgeModules", EdgeModel.EdgeModules); + + await DialogService.Show(string.Empty, parameters, options).Result; + } + + private async Task ShowAddEdgePublicModulesDialog() + { + var parameters = new DialogParameters(); + parameters.Add("EdgeModules", EdgeModel.EdgeModules); + + DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; + + await DialogService.Show(string.Empty, parameters, options).Result; + + await InvokeAsync(StateHasChanged); + } + private async Task ShowEditEdgeModuleDialog(IoTEdgeModule module) { var parameters = new DialogParameters(); @@ -414,6 +439,7 @@ else { parameters.Add("Context", Context.Edit); + parameters.Add("Module", module); await DialogService.Show(module.ModuleName, parameters, options).Result; } From 249ad5183021baeb8de32a6900e5647ce8f5069a Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 19:56:40 +0200 Subject: [PATCH 08/23] Remove dead code from AwsConfigService --- .../Services/AWS/AwsConfigService.cs | 123 +++++------------- 1 file changed, 32 insertions(+), 91 deletions(-) diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs index 44a943d92..e49e306ec 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs @@ -148,7 +148,7 @@ private async Task> CreateG { var componentArn = !string.IsNullOrEmpty(component.Id) ? $"{component.Id}:versions:{component.Version}" : // Public greengrass component - $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}"; // Private greengrass component + $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}"; // _ = await this.greengrass.DescribeComponentAsync(new DescribeComponentRequest { @@ -175,51 +175,6 @@ private async Task> CreateG return components; } - - // TODO: To delete if no more needed - //private static JObject JsonCreateComponent(IoTEdgeModule component) - //{ - - // var environmentVariableObject = new JObject(); - - // foreach (var env in component.EnvironmentVariables) - // { - // environmentVariableObject.Add(new JProperty(env.Name, env.Value)); - // } - - // var recipeJson =new JObject( - // new JProperty("RecipeFormatVersion", "2020-01-25"), - // new JProperty("ComponentName", component.ModuleName), - // new JProperty("ComponentVersion", component.Version), - // new JProperty("ComponentPublisher", "IotHub"), - // new JProperty("ComponentDependencies", - // new JObject( - // new JProperty("aws.greengrass.DockerApplicationManager", - // new JObject(new JProperty("VersionRequirement", "~2.0.0"))), - // new JProperty("aws.greengrass.TokenExchangeService", - // new JObject(new JProperty("VersionRequirement", "~2.0.0"))) - // ) - // ), - // new JProperty("Manifests", - // new JArray( - // new JObject( - // new JProperty("Platform", - // new JObject(new JProperty("os", "linux"))), - // new JProperty("Lifecycle", - // new JObject(new JProperty("Run", $"docker run {component.ImageURI}"), - // new JProperty("Environment",environmentVariableObject))), - // new JProperty("Artifacts", - // new JArray( - // new JObject(new JProperty("URI", $"docker:{component.ImageURI}")) - // ) - // ) - // ) - // ) - // ) - // ); - - // return recipeJson; - //} //AWS Not implemented methods @@ -346,13 +301,6 @@ public async Task> GetConfigModuleList(string modelId) } } - - // TODO: Delete if no more needed - //private static string retreiveImageUri(string parent, string child, string recipeJsonString) - //{ - // var uriImage = ""; - // // Parse the string as a JSON object - // var recipeJsonObject = JObject.Parse(recipeJsonString); // // Extract the "Manifests" array // var jArray = recipeJsonObject["Manifests"] as JArray; @@ -372,28 +320,26 @@ public async Task> GetConfigModuleList(string modelId) // // Extract the value of "Run" // var runValue = lifecycle[child]?.ToString(); - if (lifecycle != null) - { - // Extract the value of "Run" - var runValue = lifecycle[child]?.ToString(); + // // Search the index of the 1st whitespace + // var firstSpaceIndex = runValue.IndexOf(' '); - // Search the index of the 1st whitespace - var firstSpaceIndex = runValue.IndexOf(' '); + // if (firstSpaceIndex != -1) + // { + // // // Search the index of the 2nd whitespace + // var secondSpaceIndex = runValue.IndexOf(' ', firstSpaceIndex + 1); - if (firstSpaceIndex != -1) - { - // // Search the index of the 2nd whitespace - var secondSpaceIndex = runValue.IndexOf(' ', firstSpaceIndex + 1); + // if (secondSpaceIndex != -1) + // { + // // Extract the URI iamge + // uriImage = runValue[(secondSpaceIndex + 1)..]; + // } - if (secondSpaceIndex != -1) - { - // Extract the URI iamge - uriImage = runValue[(secondSpaceIndex + 1)..]; - } + // } + // } + // } - } - } - } + // return uriImage; + //} //private static List retreiveEnvVariableAttr(string parent, string child, string recipeJsonString) //{ @@ -421,28 +367,23 @@ public async Task> GetConfigModuleList(string modelId) // // Extract the value of "Environment" // var env = lifecycle?[child] as JObject; - if (lifecycle != null) - { - // Extract the value of "Environment" - var env = lifecycle?[child] as JObject; + // // Convert Environment JSON Object as a dictionnary + // var keyValuePairs = env!.ToObject>(); - // Convert Environment JSON Object as a dictionnary - var keyValuePairs = env!.ToObject>(); + // foreach (var kvp in keyValuePairs!) + // { + // var iotEnvVariable = new IoTEdgeModuleEnvironmentVariable + // { + // Name = kvp.Key, + // Value = kvp.Value + // }; - foreach (var kvp in keyValuePairs!) - { - var iotEnvVariable = new IoTEdgeModuleEnvironmentVariable - { - Name = kvp.Key, - Value = kvp.Value - }; - - environmentVariables.Add(iotEnvVariable); - } - } - } - return environmentVariables; - } + // environmentVariables.Add(iotEnvVariable); + // } + // } + // } + // return environmentVariables; + //} public Task> GetModelSystemModule(string modelId) { From 1518c448cfcbd95eb2a38876a671461bfa183fab Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 21:41:11 +0200 Subject: [PATCH 09/23] Fix class AwsConfigService after rebase --- .../Services/AWS/AwsConfigService.cs | 718 ++++++++---------- 1 file changed, 327 insertions(+), 391 deletions(-) diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs index e49e306ec..be28def7c 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs @@ -1,398 +1,334 @@ -// 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.Services.AWS -{ - using System.Collections.Generic; - using System.Net; - using System.Text; - using System.Threading.Tasks; - using Amazon.GreengrassV2; - using Amazon.GreengrassV2.Model; - using Amazon.IoT; - using Amazon.IoT.Model; - using AutoMapper; - using AzureIoTHub.Portal.Application.Services; - using AzureIoTHub.Portal.Domain; - using AzureIoTHub.Portal.Domain.Exceptions; - using AzureIoTHub.Portal.Domain.Repositories; - using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Shared.Models.v10; - using Configuration = Microsoft.Azure.Devices.Configuration; - - public class AwsConfigService : IConfigService - { - private readonly IAmazonGreengrassV2 greengrass; - private readonly IAmazonIoT iotClient; - - private readonly IUnitOfWork unitOfWork; - private readonly IEdgeDeviceModelRepository edgeModelRepository; - private readonly ConfigHandler config; - private readonly IMapper mapper; - - public AwsConfigService( - IAmazonGreengrassV2 greengrass, - IAmazonIoT iot, - IMapper mapper, - IUnitOfWork unitOfWork, - IEdgeDeviceModelRepository edgeModelRepository, - ConfigHandler config) - { - this.greengrass = greengrass; - this.iotClient = iot; - this.mapper = mapper; - this.unitOfWork = unitOfWork; - this.edgeModelRepository = edgeModelRepository; - this.config = config; - } - - public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) - { - - var createDeploymentRequest = new CreateDeploymentRequest - { - DeploymentName = edgeModel?.Name, - Components = await CreateGreenGrassComponents(edgeModel!), - TargetArn = await GetThingGroupArn(edgeModel!) - }; - - var createDeploymentResponse = await this.greengrass.CreateDeploymentAsync(createDeploymentRequest); - - if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) - { - throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); - - } - else - { - var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId!); - if (edgeModelEntity == null) - { - throw new Domain.Exceptions.ResourceNotFoundException($"The edge model with id {edgeModel?.ModelId} not found"); - - } - else - { - edgeModel!.ExternalIdentifier = createDeploymentResponse.DeploymentId; - - _ = this.mapper.Map(edgeModel, edgeModelEntity); - - this.edgeModelRepository.Update(edgeModelEntity); - await this.unitOfWork.SaveAsync(); - } - - } - } - - private async Task GetThingGroupArn(IoTEdgeModel edgeModel) - { - await CreateThingTypeIfNotExists(edgeModel!.Name); - - var dynamicThingGroup = new DescribeThingGroupRequest - { - ThingGroupName = edgeModel?.Name - }; - - try - { - var existingThingGroupResponse = await this.iotClient.DescribeThingGroupAsync(dynamicThingGroup); - - return existingThingGroupResponse.ThingGroupArn; - } - catch (Amazon.IoT.Model.ResourceNotFoundException) - { - var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest - { - ThingGroupName = edgeModel!.Name, - QueryString = $"thingTypeName: {edgeModel!.Name}" - }); - - return createThingGroupResponse.ThingGroupArn; - } - } - - private async Task CreateThingTypeIfNotExists(string thingTypeName) - { - var existingThingType = new DescribeThingTypeRequest - { - ThingTypeName = thingTypeName - }; - - try - { - _ = await this.iotClient.DescribeThingTypeAsync(existingThingType); - } - catch (Amazon.IoT.Model.ResourceNotFoundException) - { - _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest - { - ThingTypeName = thingTypeName, - Tags = new List - { - new Tag - { - Key = "iotEdge", - Value = "True" - } - } - }); - } - } - - private async Task> CreateGreenGrassComponents(IoTEdgeModel edgeModel) - { - var components = new Dictionary(); - foreach (var component in edgeModel.EdgeModules) - { - try +// 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.Services.AWS +{ + using System.Collections.Generic; + using System.Net; + using System.Text; + using System.Threading.Tasks; + using Amazon.GreengrassV2; + using Amazon.GreengrassV2.Model; + using Amazon.IoT; + using Amazon.IoT.Model; + using AutoMapper; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10; + using Configuration = Microsoft.Azure.Devices.Configuration; + + public class AwsConfigService : IConfigService + { + private readonly IAmazonGreengrassV2 greengrass; + private readonly IAmazonIoT iotClient; + + private readonly IUnitOfWork unitOfWork; + private readonly IEdgeDeviceModelRepository edgeModelRepository; + private readonly ConfigHandler config; + private readonly IMapper mapper; + + public AwsConfigService( + IAmazonGreengrassV2 greengrass, + IAmazonIoT iot, + IMapper mapper, + IUnitOfWork unitOfWork, + IEdgeDeviceModelRepository edgeModelRepository, + ConfigHandler config) + { + this.greengrass = greengrass; + this.iotClient = iot; + this.mapper = mapper; + this.unitOfWork = unitOfWork; + this.edgeModelRepository = edgeModelRepository; + this.config = config; + } + + public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) + { + + var createDeploymentRequest = new CreateDeploymentRequest + { + DeploymentName = edgeModel?.Name, + Components = await CreateGreenGrassComponents(edgeModel!), + TargetArn = await GetThingGroupArn(edgeModel!) + }; + + var createDeploymentResponse = await this.greengrass.CreateDeploymentAsync(createDeploymentRequest); + + if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) + { + throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); + + } + else + { + var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId!); + if (edgeModelEntity == null) + { + throw new Domain.Exceptions.ResourceNotFoundException($"The edge model with id {edgeModel?.ModelId} not found"); + + } + else + { + edgeModel!.ExternalIdentifier = createDeploymentResponse.DeploymentId; + + _ = this.mapper.Map(edgeModel, edgeModelEntity); + + this.edgeModelRepository.Update(edgeModelEntity); + await this.unitOfWork.SaveAsync(); + } + + } + } + + private async Task GetThingGroupArn(IoTEdgeModel edgeModel) + { + await CreateThingTypeIfNotExists(edgeModel!.Name); + + var dynamicThingGroup = new DescribeThingGroupRequest + { + ThingGroupName = edgeModel?.Name + }; + + try + { + var existingThingGroupResponse = await this.iotClient.DescribeThingGroupAsync(dynamicThingGroup); + + return existingThingGroupResponse.ThingGroupArn; + } + catch (Amazon.IoT.Model.ResourceNotFoundException) + { + var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest + { + ThingGroupName = edgeModel!.Name, + QueryString = $"thingTypeName: {edgeModel!.Name}" + }); + + return createThingGroupResponse.ThingGroupArn; + } + } + + private async Task CreateThingTypeIfNotExists(string thingTypeName) + { + var existingThingType = new DescribeThingTypeRequest + { + ThingTypeName = thingTypeName + }; + + try + { + _ = await this.iotClient.DescribeThingTypeAsync(existingThingType); + } + catch (Amazon.IoT.Model.ResourceNotFoundException) + { + _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest + { + ThingTypeName = thingTypeName, + Tags = new List + { + new Tag + { + Key = "iotEdge", + Value = "True" + } + } + }); + } + } + + private async Task> CreateGreenGrassComponents(IoTEdgeModel edgeModel) + { + var components = new Dictionary(); + foreach (var component in edgeModel.EdgeModules) + { + try { var componentArn = !string.IsNullOrEmpty(component.Id) ? $"{component.Id}:versions:{component.Version}" : // Public greengrass component $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}"; // - - _ = await this.greengrass.DescribeComponentAsync(new DescribeComponentRequest - { - Arn = componentArn - }); - components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); - - } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) - { - var componentVersion = new CreateComponentVersionRequest - { - InlineRecipe = new MemoryStream(Encoding.UTF8.GetBytes(component.ContainerCreateOptions)) - }; - var response = await greengrass.CreateComponentVersionAsync(componentVersion); - if (response.HttpStatusCode != HttpStatusCode.Created) - { - throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API."); - - } - components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); - } + + _ = await this.greengrass.DescribeComponentAsync(new DescribeComponentRequest + { + Arn = componentArn + }); + components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); + + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) + { + var componentVersion = new CreateComponentVersionRequest + { + InlineRecipe = new MemoryStream(Encoding.UTF8.GetBytes(component.ContainerCreateOptions)) + }; + var response = await greengrass.CreateComponentVersionAsync(componentVersion); + if (response.HttpStatusCode != HttpStatusCode.Created) + { + throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API."); + + } + components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); + } } - - return components; - } - - //AWS Not implemented methods - - public Task> GetIoTEdgeConfigurations() - { - throw new NotImplementedException(); - } - - public Task> GetDevicesConfigurations() - { - throw new NotImplementedException(); - } - - public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) - { - throw new NotImplementedException(); - } - - public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) - { - throw new NotImplementedException(); - } - - public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) - { - throw new NotImplementedException(); - } - - public Task GetConfigItem(string id) - { - throw new NotImplementedException(); - } - - public async Task DeleteConfiguration(string modelId) - { - var modules = await GetConfigModuleList(modelId); - foreach (var module in modules) - { - var deletedComponentResponse = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest - { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" - }); - - if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) - { - throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); - - } - } - - var cancelDeploymentResponse = await this.greengrass.CancelDeploymentAsync(new CancelDeploymentRequest - { - DeploymentId = modelId - }); - if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) - { - throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); - - } - else - { - var deleteDeploymentResponse = await this.greengrass.DeleteDeploymentAsync(new DeleteDeploymentRequest - { - DeploymentId = modelId - }); - - if (deleteDeploymentResponse.HttpStatusCode != HttpStatusCode.NoContent) - { - throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API."); - } - } - - } - - public Task GetFailedDeploymentsCount() - { - throw new NotImplementedException(); - } - - public async Task> GetConfigModuleList(string modelId) - { - - var moduleList = new List(); - - var getDeployement = new GetDeploymentRequest - { - DeploymentId = modelId, - }; - try - { - var response = await this.greengrass.GetDeploymentAsync(getDeployement); - - foreach (var compoenent in response.Components) - { - var responseComponent = await this.greengrass.GetComponentAsync(new GetComponentRequest - { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", - RecipeOutputFormat = RecipeOutputFormat.JSON - }); - - // Read the Recipe which is in JSON Format - using var reader = new StreamReader(responseComponent.Recipe); - var recipeJsonString = reader.ReadToEnd(); - - var iotEdgeModule = new IoTEdgeModule + + return components; + } + + //AWS Not implemented methods + + public Task> GetIoTEdgeConfigurations() + { + throw new NotImplementedException(); + } + + public Task> GetDevicesConfigurations() + { + throw new NotImplementedException(); + } + + public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) + { + throw new NotImplementedException(); + } + + public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) + { + throw new NotImplementedException(); + } + + public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) + { + throw new NotImplementedException(); + } + + public Task GetConfigItem(string id) + { + throw new NotImplementedException(); + } + + public async Task DeleteConfiguration(string modelId) + { + var modules = await GetConfigModuleList(modelId); + foreach (var module in modules) + { + var deletedComponentResponse = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" + }); + + if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) + { + throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); + + } + } + + var cancelDeploymentResponse = await this.greengrass.CancelDeploymentAsync(new CancelDeploymentRequest + { + DeploymentId = modelId + }); + if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) + { + throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); + + } + else + { + var deleteDeploymentResponse = await this.greengrass.DeleteDeploymentAsync(new DeleteDeploymentRequest + { + DeploymentId = modelId + }); + + if (deleteDeploymentResponse.HttpStatusCode != HttpStatusCode.NoContent) + { + throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API."); + } + } + + } + + public Task GetFailedDeploymentsCount() + { + throw new NotImplementedException(); + } + + public async Task> GetConfigModuleList(string modelId) + { + + var moduleList = new List(); + + var getDeployement = new GetDeploymentRequest + { + DeploymentId = modelId, + }; + try + { + var response = await this.greengrass.GetDeploymentAsync(getDeployement); + + foreach (var compoenent in response.Components) + { + var componentId = string.Empty; + var jsonRecipe = string.Empty; + + try + { + var responseComponent = await this.greengrass.GetComponentAsync(new GetComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", + RecipeOutputFormat = RecipeOutputFormat.JSON + }); + + // Read the Recipe which is in JSON Format + using var reader = new StreamReader(responseComponent.Recipe); + jsonRecipe = reader.ReadToEnd(); + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) { - Id = componentId, - ModuleName = compoenent.Key, + // If the component is not found, we assume it is a public component + componentId = $"arn:aws:greengrass:{config.AWSRegion}:aws:components:{compoenent.Key}"; + + var responseComponent = await this.greengrass.GetComponentAsync(new GetComponentRequest + { + Arn = $"arn:aws:greengrass:{config.AWSRegion}:aws:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", + RecipeOutputFormat = RecipeOutputFormat.JSON + }); + + using var reader = new StreamReader(responseComponent.Recipe); + jsonRecipe = reader.ReadToEnd(); + } + + var iotEdgeModule = new IoTEdgeModule + { + Id = componentId, + ModuleName = compoenent.Key, Version = compoenent.Value.ComponentVersion, - ContainerCreateOptions = recipeJsonString, - // ImageURI is required, but not used for Greengrass components - ImageURI = "example.com" - }; - - } - - - } - return moduleList; - } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) - { - throw new InternalServerErrorException("The deployment is not found"); - - } - } - - // // Extract the "Manifests" array - // var jArray = recipeJsonObject["Manifests"] as JArray; - // var manifests = jArray; - - // if (manifests != null && manifests.Count > 0) - // { - // // Get the first manifest in the array - // var firstManifest = manifests[0] as JObject; - - // // Extract the "Lifecycle" object - // var jObject = firstManifest?[parent] as JObject; - // var lifecycle = jObject; - - // if (lifecycle != null) - // { - // // Extract the value of "Run" - // var runValue = lifecycle[child]?.ToString(); - - // // Search the index of the 1st whitespace - // var firstSpaceIndex = runValue.IndexOf(' '); - - // if (firstSpaceIndex != -1) - // { - // // // Search the index of the 2nd whitespace - // var secondSpaceIndex = runValue.IndexOf(' ', firstSpaceIndex + 1); - - // if (secondSpaceIndex != -1) - // { - // // Extract the URI iamge - // uriImage = runValue[(secondSpaceIndex + 1)..]; - // } - - // } - // } - // } - - // return uriImage; - //} - - //private static List retreiveEnvVariableAttr(string parent, string child, string recipeJsonString) - //{ - - // // Parse the string as a JSON object - // var recipeJsonObject = JObject.Parse(recipeJsonString); - - // var environmentVariables = new List(); - - // // Extract the "Manifests" array - // var jArray = recipeJsonObject["Manifests"] as JArray; - // var manifests = jArray; - - // if (manifests != null && manifests.Count > 0) - // { - // // Get the first manifest in the array - // var firstManifest = manifests[0] as JObject; - - // // Extract the "Lifecycle" object - // var jObject = firstManifest?[parent] as JObject; - // var lifecycle = jObject; - - // if (lifecycle != null) - // { - // // Extract the value of "Environment" - // var env = lifecycle?[child] as JObject; - - // // Convert Environment JSON Object as a dictionnary - // var keyValuePairs = env!.ToObject>(); - - // foreach (var kvp in keyValuePairs!) - // { - // var iotEnvVariable = new IoTEdgeModuleEnvironmentVariable - // { - // Name = kvp.Key, - // Value = kvp.Value - // }; - - // environmentVariables.Add(iotEnvVariable); - // } - // } - // } - // return environmentVariables; - //} - - public Task> GetModelSystemModule(string modelId) - { - throw new NotImplementedException(); - } - - public Task> GetConfigRouteList(string modelId) - { - throw new NotImplementedException(); + ContainerCreateOptions = jsonRecipe, + // ImageURI is required, but not used for Greengrass components + ImageURI = "example.com" + }; + + moduleList.Add(iotEdgeModule); + + } + return moduleList; + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) + { + throw new InternalServerErrorException("The deployment is not found"); + + } + } + + public Task> GetModelSystemModule(string modelId) + { + throw new NotImplementedException(); + } + + public Task> GetConfigRouteList(string modelId) + { + throw new NotImplementedException(); } public async Task> GetPublicEdgeModules() @@ -423,6 +359,6 @@ public async Task> GetPublicEdgeModules() // ImageURI is required, but not used for Greengrass components ImageURI = "example.com" }); - } - } -} + } + } +} From d501e81c2e153182464c5b85a7ed2f7d1fe6345b Mon Sep 17 00:00:00 2001 From: Kevin BEAUGRAND Date: Thu, 1 Jun 2023 21:45:56 +0200 Subject: [PATCH 10/23] Fix #2145 - Move Savechanges at last step of CRUD in edge model service --- .../Services/IEdgeModelService.cs | 64 +++++++++---------- .../Services/EdgeModelService.cs | 17 ++--- .../Server/Services/EdgeModelServiceTest.cs | 54 ---------------- 3 files changed, 40 insertions(+), 95 deletions(-) diff --git a/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs b/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs index ba522ce24..8e430ea07 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IEdgeModelService.cs @@ -1,33 +1,31 @@ -// 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.Application.Services -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Shared.Models.v10.Filters; - using Microsoft.AspNetCore.Http; - - public interface IEdgeModelService - { - Task> GetEdgeModels(EdgeModelFilter edgeModelFilter); - - Task GetEdgeModel(string modelId); - - Task CreateEdgeModel(IoTEdgeModel edgeModel); - Task UpdateEdgeModel(IoTEdgeModel edgeModel); - - Task DeleteEdgeModel(string edgeModelId); - - Task GetEdgeModelAvatar(string edgeModelId); - - Task UpdateEdgeModelAvatar(string edgeModelId, IFormFile file); - - Task DeleteEdgeModelAvatar(string edgeModelId); - - Task SaveModuleCommands(IoTEdgeModel deviceModelObject); - - Task> GetPublicEdgeModules(); - } -} +// 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.Application.Services +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10.Filters; + using Microsoft.AspNetCore.Http; + + public interface IEdgeModelService + { + Task> GetEdgeModels(EdgeModelFilter edgeModelFilter); + + Task GetEdgeModel(string modelId); + + Task CreateEdgeModel(IoTEdgeModel edgeModel); + Task UpdateEdgeModel(IoTEdgeModel edgeModel); + + Task DeleteEdgeModel(string edgeModelId); + + Task GetEdgeModelAvatar(string edgeModelId); + + Task UpdateEdgeModelAvatar(string edgeModelId, IFormFile file); + + Task DeleteEdgeModelAvatar(string edgeModelId); + + Task> GetPublicEdgeModules(); + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs index 6f2e5bcb2..9030a9898 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs @@ -104,11 +104,11 @@ public async Task> GetEdgeModels(EdgeModelFilt public async Task CreateEdgeModel(IoTEdgeModel edgeModel) { var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId); + if (edgeModelEntity == null) { edgeModelEntity = this.mapper.Map(edgeModel); await this.edgeModelRepository.InsertAsync(edgeModelEntity); - await this.unitOfWork.SaveAsync(); } else { @@ -123,8 +123,9 @@ public async Task CreateEdgeModel(IoTEdgeModel edgeModel) await SaveModuleCommands(edgeModel); } - await this.configService.RollOutEdgeModelConfiguration(edgeModel); - + await this.configService.RollOutEdgeModelConfiguration(edgeModel); + + await this.unitOfWork.SaveAsync(); } /// @@ -133,9 +134,8 @@ public async Task CreateEdgeModel(IoTEdgeModel edgeModel) /// The device model object. /// /// - public async Task SaveModuleCommands(IoTEdgeModel deviceModelObject) + private async Task SaveModuleCommands(IoTEdgeModel deviceModelObject) { - IEnumerable moduleCommands = deviceModelObject.EdgeModules .SelectMany(x => x.Commands.Select(cmd => new IoTEdgeModuleCommand { @@ -146,6 +146,7 @@ public async Task SaveModuleCommands(IoTEdgeModel deviceModelObject) })).ToArray(); var existingCommands = this.commandRepository.GetAll().Where(x => x.EdgeDeviceModelId == deviceModelObject.ModelId).ToList(); + foreach (var command in existingCommands) { this.commandRepository.Delete(command.Id); @@ -155,7 +156,6 @@ public async Task SaveModuleCommands(IoTEdgeModel deviceModelObject) { await this.commandRepository.InsertAsync(this.mapper.Map(cmd)); } - await this.unitOfWork.SaveAsync(); } /// @@ -262,12 +262,13 @@ public async Task UpdateEdgeModel(IoTEdgeModel edgeModel) _ = this.mapper.Map(edgeModel, edgeModelEntity); this.edgeModelRepository.Update(edgeModelEntity); - await this.unitOfWork.SaveAsync(); await SaveModuleCommands(edgeModel); } - await this.configService.RollOutEdgeModelConfiguration(edgeModel); + await this.configService.RollOutEdgeModelConfiguration(edgeModel); + + await this.unitOfWork.SaveAsync(); } /// diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index a02deb186..aaee8d1a7 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -618,59 +618,5 @@ public async Task DeleteEdgeDeviceModelAvatarShouldDeleteEdgeDeviceModelAvatar() // Assert MockRepository.VerifyAll(); } - - [Test] - public async Task SaveModuleCommandsShouldUpdateDatabase() - { - // Arrange - var edgeDeviceModel = Fixture.Create(); - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = edgeDeviceModel.ModelId; - return command; - }) .ToList(); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) - .Returns(expectedCommands); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - // Act - await this.edgeDeviceModelService.SaveModuleCommands(edgeDeviceModel); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public void SaveModuleCommandsShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs() - { - // Arrange - var edgeDeviceModel = Fixture.Create(); - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = edgeDeviceModel.ModelId; - return command; - }) .ToList(); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) - .Returns(expectedCommands); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Throws(new DbUpdateException()); - - // Act - var result = async () => await this.edgeDeviceModelService.SaveModuleCommands(edgeDeviceModel); - - // Assert - _ = result.Should().ThrowAsync(); - } } } From d4004ad63bcfdcda093750a4a235c55d467925eb Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 22:02:44 +0200 Subject: [PATCH 11/23] Fix duplication issue after selecting public components --- .../Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor index 7602c9a46..3602a2103 100644 --- a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor +++ b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor @@ -36,14 +36,15 @@ protected async override Task OnInitializedAsync() { - selectedPublicComponents = new HashSet(EdgeModules.Where(m => !string.IsNullOrEmpty(m.Id))); - publicComponents = await EdgeModelClientService.GetPublicEdgeModules(); + publicComponents = await EdgeModelClientService.GetPublicEdgeModules(); + selectedPublicComponents = new HashSet(publicComponents.Where(m => EdgeModules.Any(e => m.Id.Equals(e.Id)))); } private void Cancel() => MudDialog.Cancel(); private void Submit() { + EdgeModules.RemoveAll(e => !string.IsNullOrEmpty(e.Id)); EdgeModules.AddRange(selectedPublicComponents.ToList()); MudDialog.Close(DialogResult.Ok(true)); From df68ce67c543109d72a7f1c8f95d259ccc7eb548 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Thu, 1 Jun 2023 22:15:51 +0200 Subject: [PATCH 12/23] Fix unit tests --- .../Services/AWS_Tests/AwsConfigTests.cs | 20 ++++++++++++++----- .../Server/Services/EdgeModelServiceTest.cs | 3 +++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs index 2fa4c0b8a..1c89f2728 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs @@ -26,8 +26,9 @@ namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Services.AWS_Tests using System.Collections.Generic; using System.IO; using System.Text; - using Newtonsoft.Json.Linq; - + using Newtonsoft.Json.Linq; + using System.Linq; + [TestFixture] public class AwsConfigTests : BackendUnitTest { @@ -75,7 +76,10 @@ public async Task CreateDeploymentWithComponentsAndExistingThingGroupAndThingTyp _ = this.mockConfigHandler.Setup(handler => handler.AWSRegion).Returns("eu-west-1"); _ = this.mockConfigHandler.Setup(handler => handler.AWSAccountId).Returns("00000000"); - var edge = Fixture.Create(); + var edge = Fixture.Create(); + // Simulate a custom/private component + edge.EdgeModules.First().Id = string.Empty; + var edgeDeviceModelEntity = Mapper.Map(edge); _ = this.mockIotClient.Setup(s3 => s3.DescribeThingGroupAsync(It.IsAny(), It.IsAny())) @@ -124,7 +128,10 @@ public async Task CreateDeploymentWithExistingComponentsAndExistingThingGroupAnd _ = this.mockConfigHandler.Setup(handler => handler.AWSRegion).Returns("eu-west-1"); _ = this.mockConfigHandler.Setup(handler => handler.AWSAccountId).Returns("00000000"); - var edge = Fixture.Create(); + var edge = Fixture.Create(); + // Simulate a custom/private component + edge.EdgeModules.First().Id = string.Empty; + var edgeDeviceModelEntity = Mapper.Map(edge); _ = this.mockIotClient.Setup(s3 => s3.DescribeThingGroupAsync(It.IsAny(), It.IsAny())) @@ -170,7 +177,10 @@ public async Task CreateDeploymentWithNonExistingComponentsAndNonExistingThingGr _ = this.mockConfigHandler.Setup(handler => handler.AWSRegion).Returns("eu-west-1"); _ = this.mockConfigHandler.Setup(handler => handler.AWSAccountId).Returns("00000000"); - var edge = Fixture.Create(); + var edge = Fixture.Create(); + // Simulate a custom/private component + edge.EdgeModules.First().Id = string.Empty; + var edgeDeviceModelEntity = Mapper.Map(edge); _ = this.mockIotClient.Setup(s3 => s3.DescribeThingGroupAsync(It.IsAny(), It.IsAny())) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index aaee8d1a7..0717ca6f0 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -405,6 +405,9 @@ public async Task UpdateEdgeModelForAWSShouldUpdateEdgeModel() .Verifiable(); _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) + .Returns(Task.CompletedTask); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) .Returns(Task.CompletedTask); // Act From eea16fdc5240c62c868a1641a093e93912010816 Mon Sep 17 00:00:00 2001 From: Kevin BEAUGRAND Date: Fri, 2 Jun 2023 08:22:28 +0200 Subject: [PATCH 13/23] Fix deployment model synchronization --- .../Jobs/AWS/SyncGreenGrassDeploymentsJob.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs index bb27e5bea..c591877fe 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs @@ -115,9 +115,8 @@ private async Task> GetAllGreenGrassDeployments() private async Task CreateNonExisitingGreenGrassDeployment(IoTEdgeModel iotEdgeModel) { - var iotEdgeModels = (await this.edgeDeviceModelRepository.GetAllAsync()) - .Where(edge => edge.ExternalIdentifier!.Equals(iotEdgeModel.ExternalIdentifier, StringComparison.Ordinal)).ToList(); + .Where(edge => string.Equals(edge.ExternalIdentifier, iotEdgeModel.ExternalIdentifier, StringComparison.Ordinal)).ToList(); if (iotEdgeModels.Count == 0) { @@ -138,7 +137,7 @@ private async Task DeleteGreenGrassDeployments(List edgeModels) { //Get All Deployments that are not in AWS var deploymentToDelete = (await this.edgeDeviceModelRepository.GetAllAsync()) - .Where(edge => !edgeModels.Any(edgeModel => edge.ExternalIdentifier!.Equals(edgeModel.ExternalIdentifier, StringComparison.Ordinal))) + .Where(edge => !edgeModels.Any(edgeModel => string.Equals(edge.ExternalIdentifier, edgeModel.ExternalIdentifier, StringComparison.Ordinal))) .ToList(); foreach (var edgeModel in deploymentToDelete) From 9457394cff360ed628212edddcfc7a1378b9ea82 Mon Sep 17 00:00:00 2001 From: Kevin BEAUGRAND Date: Fri, 2 Jun 2023 08:35:26 +0200 Subject: [PATCH 14/23] Fix Edgedevice load and update --- .../Services/IConfigService.cs | 8 +- .../EdgeDevices/EdgeDeviceDetailPage.razor | 3 +- .../Services/AWS/AWSEdgeDevicesService.cs | 10 +- .../Services/AWS/AwsConfigService.cs | 33 +- .../Services/AwsExternalDeviceService.cs | 23 +- .../Services/EdgeModelService.cs | 4 +- .../Services/ConfigService.cs | 712 +++++----- .../Services/DeviceConfigurationsService.cs | 2 +- .../Services/DeviceModelService.cs | 2 +- .../Services/AWS_Tests/AwsConfigTests.cs | 61 +- .../Server/Services/ConfigServiceTests.cs | 12 +- .../DeviceConfigurationsServiceTest.cs | 16 +- .../Services/DeviceModelServiceTests.cs | 4 +- .../Server/Services/EdgeModelServiceTest.cs | 1246 ++++++++--------- 14 files changed, 1059 insertions(+), 1077 deletions(-) diff --git a/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs b/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs index 5b894a266..239cf2ab6 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IConfigService.cs @@ -4,7 +4,7 @@ namespace AzureIoTHub.Portal.Application.Services { using System.Collections.Generic; - using System.Threading.Tasks; + using System.Threading.Tasks; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Shared.Models.v10; using Microsoft.Azure.Devices; @@ -15,13 +15,13 @@ public interface IConfigService Task> GetDevicesConfigurations(); - Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties); + Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties); Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix); - Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel); + Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel); - Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0); + Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0); Task GetConfigItem(string id); diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor index abb8f338f..ce6c2db8c 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor @@ -117,7 +117,8 @@ Label="Device name" Variant="Variant.Outlined" For="@(()=> edgeDevice.DeviceName)" - Required="true" /> + Required="true" + ReadOnly=@(Portal.CloudProvider == CloudProviders.AWS)/> diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs index 673dc99f0..0ce7a2aae 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs @@ -96,9 +96,8 @@ public async Task UpdateEdgeDevice(IoTEdgeDevice edgeDevice) { ArgumentNullException.ThrowIfNull(edgeDevice, nameof(edgeDevice)); - _ = await this.awsExternalDevicesService.GetDevice(edgeDevice.DeviceId); + _ = await this.awsExternalDevicesService.GetDevice(edgeDevice.DeviceName); - // TODO var result = await UpdateEdgeDeviceInDatabase(edgeDevice); await this.unitOfWork.SaveAsync(); @@ -131,8 +130,11 @@ public async Task GetEdgeDevice(string edgeDeviceId) var deviceDto = await base.GetEdgeDevice(edgeDeviceId); deviceDto.LastDeployment = await this.externalDeviceService.RetrieveLastConfiguration(deviceDto); - deviceDto.RuntimeResponse = deviceDto.LastDeployment.Status; - deviceDto.Modules = await this.configService.GetConfigModuleList(deviceDto.ModelId); + deviceDto.RuntimeResponse = deviceDto.LastDeployment?.Status; + + var model = await this.deviceModelRepository.GetByIdAsync(deviceDto.ModelId); + + deviceDto.Modules = await this.configService.GetConfigModuleList(model.ExternalIdentifier!); deviceDto.NbDevices = await this.awsExternalDevicesService.GetEdgeDeviceNbDevices(deviceDto); deviceDto.NbModules = deviceDto.Modules.Count; diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs index be28def7c..9f5f6237a 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs @@ -46,7 +46,7 @@ public AwsConfigService( this.config = config; } - public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) + public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) { var createDeploymentRequest = new CreateDeploymentRequest @@ -61,27 +61,9 @@ public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) { throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); - } - else - { - var edgeModelEntity = await this.edgeModelRepository.GetByIdAsync(edgeModel?.ModelId!); - if (edgeModelEntity == null) - { - throw new Domain.Exceptions.ResourceNotFoundException($"The edge model with id {edgeModel?.ModelId} not found"); - - } - else - { - edgeModel!.ExternalIdentifier = createDeploymentResponse.DeploymentId; - - _ = this.mapper.Map(edgeModel, edgeModelEntity); - this.edgeModelRepository.Update(edgeModelEntity); - await this.unitOfWork.SaveAsync(); - } - - } + return createDeploymentResponse.DeploymentId; } private async Task GetThingGroupArn(IoTEdgeModel edgeModel) @@ -188,7 +170,7 @@ public Task> GetDevicesConfigurations() throw new NotImplementedException(); } - public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) + public Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) { throw new NotImplementedException(); } @@ -198,7 +180,7 @@ public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string confi throw new NotImplementedException(); } - public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) + public Task RollOutDeviceConfiguration(string modelId, Dictionary desiredProperties, string configurationId, Dictionary targetTags, int priority = 0) { throw new NotImplementedException(); } @@ -211,7 +193,8 @@ public Task GetConfigItem(string id) public async Task DeleteConfiguration(string modelId) { var modules = await GetConfigModuleList(modelId); - foreach (var module in modules) + + foreach (var module in modules.Where(c => string.IsNullOrEmpty(c.Id))) { var deletedComponentResponse = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest { @@ -221,7 +204,6 @@ public async Task DeleteConfiguration(string modelId) if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) { throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); - } } @@ -229,6 +211,7 @@ public async Task DeleteConfiguration(string modelId) { DeploymentId = modelId }); + if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) { throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); @@ -246,7 +229,6 @@ public async Task DeleteConfiguration(string modelId) throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API."); } } - } public Task GetFailedDeploymentsCount() @@ -256,7 +238,6 @@ public Task GetFailedDeploymentsCount() public async Task> GetConfigModuleList(string modelId) { - var moduleList = new List(); var getDeployement = new GetDeploymentRequest diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs index 5c49acedc..314a30e7e 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs @@ -252,17 +252,24 @@ public async Task GetDeviceCredentials(string deviceName) public async Task RetrieveLastConfiguration(IoTEdgeDevice ioTEdgeDevice) { - var coreDevice = await this.greengrass.GetCoreDeviceAsync(new GetCoreDeviceRequest + try { - CoreDeviceThingName = ioTEdgeDevice.DeviceName - }); + var coreDevice = await this.greengrass.GetCoreDeviceAsync(new GetCoreDeviceRequest + { + CoreDeviceThingName = ioTEdgeDevice.DeviceName + }); - return new ConfigItem + return new ConfigItem + { + Name = coreDevice.CoreDeviceThingName, + DateCreation = coreDevice.LastStatusUpdateTimestamp, + Status = coreDevice.Status + }; + } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) { - Name = coreDevice.CoreDeviceThingName, - DateCreation = coreDevice.LastStatusUpdateTimestamp, - Status = coreDevice.Status - }; + return null!; + } } public Task UpdateDevice(Device device) diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs index 9030a9898..427f1ae63 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/EdgeModelService.cs @@ -123,7 +123,7 @@ public async Task CreateEdgeModel(IoTEdgeModel edgeModel) await SaveModuleCommands(edgeModel); } - await this.configService.RollOutEdgeModelConfiguration(edgeModel); + edgeModelEntity.ExternalIdentifier = await this.configService.RollOutEdgeModelConfiguration(edgeModel); await this.unitOfWork.SaveAsync(); } @@ -266,7 +266,7 @@ public async Task UpdateEdgeModel(IoTEdgeModel edgeModel) await SaveModuleCommands(edgeModel); } - await this.configService.RollOutEdgeModelConfiguration(edgeModel); + edgeModel.ExternalIdentifier = await this.configService.RollOutEdgeModelConfiguration(edgeModel); await this.unitOfWork.SaveAsync(); } diff --git a/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs b/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs index 1dfde207a..14a6a66d9 100644 --- a/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs +++ b/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs @@ -1,359 +1,365 @@ -// 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.Server.Services -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using Azure; - using AzureIoTHub.Portal.Application.Helpers; - using AzureIoTHub.Portal.Application.Services; - using AzureIoTHub.Portal.Crosscutting.Extensions; - using AzureIoTHub.Portal.Domain.Exceptions; - using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Shared.Models.v10; - using Microsoft.Azure.Devices; - using Microsoft.Azure.Devices.Common.Extensions; - using Newtonsoft.Json.Linq; - - public class ConfigService : IConfigService - { - private readonly RegistryManager registryManager; - - public ConfigService( - RegistryManager registry) - { - this.registryManager = registry; - } - - public async Task> GetIoTEdgeConfigurations() - { - try - { - var configurations = await this.registryManager.GetConfigurationsAsync(0); - return configurations.Where(c => c.Content.ModulesContent.Count > 0); - } - catch (RequestFailedException ex) - { - throw new InternalServerErrorException("Unable to get IOT Edge configurations", ex); - } - } - - public async Task> GetDevicesConfigurations() - { - try - { - var configurations = await this.registryManager.GetConfigurationsAsync(0); - - return configurations - .Where(c => c.Priority > 0 && c.Content.ModulesContent.Count == 0); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException("Unable to get devices configurations", e); - } - } - - public async Task GetConfigItem(string id) - { - try - { - return await this.registryManager.GetConfigurationAsync(id); - } - catch (RequestFailedException ex) - { - throw new InternalServerErrorException($"Unable to get the configuration for id {id}", ex); - } - } - - public async Task> GetConfigModuleList(string modelId) - { - var configList = await GetIoTEdgeConfigurations(); - - var config = configList.FirstOrDefault((x) => x.Id.StartsWith(modelId, StringComparison.Ordinal)); - - if (config == null) - { - throw new InternalServerErrorException("Config does not exist."); - } - - var moduleList = new List(); - - // Details of every modules are stored within the EdgeAgent module data - if (config.Content.ModulesContent != null - && config.Content.ModulesContent.TryGetValue("$edgeAgent", out var edgeAgentModule) - && edgeAgentModule.TryGetValue("properties.desired", out var edgeAgentDesiredProperties)) - { - // Converts the object to a JObject to access its properties more easily - if (edgeAgentDesiredProperties is not JObject modObject) - { - throw new InvalidOperationException($"Could not parse properties.desired for the configuration id {config.Id}"); - } - - // Adds regular modules to the list of modules - if (modObject.TryGetValue("modules", out var modules)) - { - foreach (var newModule in modules.Values().Select(module => ConfigHelper.CreateGatewayModule(config, module))) - { - newModule.ModuleIdentityTwinSettings = ConfigHelper.CreateModuleTwinSettings(config.Content.ModulesContent, newModule.ModuleName); - moduleList.Add(newModule); - } - } - } - - return moduleList; - } - - public async Task> GetModelSystemModule(string modelId) - { - var configList = await GetIoTEdgeConfigurations(); - - var config = configList.FirstOrDefault((x) => x.Id.StartsWith(modelId, StringComparison.Ordinal)); - - if (config == null) - { - throw new InternalServerErrorException("Config does not exist."); - } - - var moduleList = new List(); - - // Details of every modules are stored within the EdgeAgent module data - if (config.Content.ModulesContent != null - && config.Content.ModulesContent.TryGetValue("$edgeAgent", out var edgeAgentModule) - && edgeAgentModule.TryGetValue("properties.desired", out var edgeAgentDesiredProperties)) - { - // Converts the object to a JObject to access its properties more easily - if (edgeAgentDesiredProperties is not JObject modObject) - { - throw new InvalidOperationException($"Could not parse properties.desired for the configuration id {config.Id}"); - } - - // Adds regular modules to the list of modules - if (modObject.TryGetValue("systemModules", out var modules)) - { - foreach (var newModule in modules.Values().Select(module => ConfigHelper.CreateGatewayModule(config, module))) - { - moduleList.Add(new EdgeModelSystemModule(newModule.ModuleName) - { - ImageUri = newModule.ImageURI, - EnvironmentVariables = newModule.EnvironmentVariables, - ContainerCreateOptions = newModule.ContainerCreateOptions, - }); - } - } - } - - return moduleList; - } - - public async Task> GetConfigRouteList(string modelId) - { - var configList = await GetIoTEdgeConfigurations(); - - var config = configList.FirstOrDefault((x) => x.Id.StartsWith(modelId, StringComparison.Ordinal)); - - if (config == null) - { - throw new InternalServerErrorException("Config does not exist."); - } - - var routeList = new List(); - - // Details of routes are stored within the EdgeHub properties.desired - if (config.Content.ModulesContent != null - && config.Content.ModulesContent.TryGetValue("$edgeHub", out var edgeHubModule) - && edgeHubModule.TryGetValue("properties.desired", out var edgeHubDesiredProperties)) - { - // - if (edgeHubDesiredProperties is not JObject modObject) - { - throw new InvalidOperationException($"Could not parse properties.desired for the configuration id {config.Id}"); - } - - // - if (modObject.TryGetValue("routes", out var routes)) - { - foreach (var newRoute in routes.Values().Select(route => ConfigHelper.CreateIoTEdgeRouteFromJProperty(route))) - { - routeList.Add(newRoute); - } - } - } - - return routeList; - - } - - public async Task DeleteConfiguration(string configId) - { - try - { - await this.registryManager.RemoveConfigurationAsync(configId); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to delete the configuration for id {configId}", e); - } - } - - public async Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) - { -#pragma warning disable CA1308 // Normalize strings to uppercase - var configurationNamePrefix = modelId?.Trim() - .ToLowerInvariant() - .Replace(" ", "-", StringComparison.OrdinalIgnoreCase); -#pragma warning restore CA1308 // Normalize strings to uppercase - - await DeleteDeviceModelConfigurationByConfigurationNamePrefix(configurationNamePrefix); - - var newConfiguration = new Configuration($"{configurationNamePrefix}-{DateTime.UtcNow.Ticks}"); - - newConfiguration.Labels.Add("created-by", "Azure IoT hub Portal"); - newConfiguration.TargetCondition = $"tags.modelId = '{modelId}'"; - newConfiguration.Content.DeviceContent = desiredProperties; - - _ = await this.registryManager.AddConfigurationAsync(newConfiguration); - } - - public async Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) - { - var configurations = await this.registryManager.GetConfigurationsAsync(0); - - foreach (var item in configurations) - { - if (!item.Id.StartsWith(configurationNamePrefix, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - await this.registryManager.RemoveConfigurationAsync(item.Id); - } - } - - public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) - { - var configurations = await this.registryManager.GetConfigurationsAsync(0); - - var configurationNamePrefix = edgeModel.ModelId?.Trim() - .ToLowerInvariant() - .Replace(" ", "-", StringComparison.OrdinalIgnoreCase); - - var newConfiguration = new Configuration($"{configurationNamePrefix}-{DateTime.UtcNow.Ticks}"); - newConfiguration.Labels.Add("created-by", "Azure IoT hub Portal"); - newConfiguration.TargetCondition = $"tags.modelId = '{edgeModel.ModelId}'"; - newConfiguration.Priority = 10; - - newConfiguration.Content.ModulesContent = ConfigHelper.GenerateModulesContent(edgeModel); - - try - { - _ = await this.registryManager.AddConfigurationAsync(newConfiguration); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException("Unable to create configuration.", e); - } - - foreach (var item in configurations) - { - if (!item.Id.StartsWith(configurationNamePrefix, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - await this.registryManager.RemoveConfigurationAsync(item.Id); - } - } - - public async Task RollOutDeviceConfiguration( - string modelId, - Dictionary desiredProperties, - string configurationId, - Dictionary targetTags, - int priority = 0) - { - IEnumerable configurations; - - try - { - configurations = await this.registryManager.GetConfigurationsAsync(0); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException("Unable to get configurations", e); - } - - var configurationNamePrefix = configurationId.Trim().ToLowerInvariant().RemoveDiacritics(); - - foreach (var item in configurations) - { - if (!item.Id.StartsWith(configurationNamePrefix, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - try - { - await this.registryManager.RemoveConfigurationAsync(item.Id); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to remove configuration {item.Id}", e); - } - } - - var newConfiguration = new Configuration($"{configurationNamePrefix}-{DateTime.UtcNow.Ticks}"); - - newConfiguration.Labels.Add("created-by", "Azure IoT hub Portal"); - newConfiguration.Labels.Add("configuration-id", configurationId); - - var culture = CultureInfo.CreateSpecificCulture("en-En"); - var targetCondition = new StringBuilder(); - - foreach (var item in targetTags) - { - _ = targetCondition.AppendFormat(culture, " and tags.{0}", item.Key); - _ = targetCondition.AppendFormat(culture, " = '{0}'", item.Value); - } - - newConfiguration.TargetCondition = $"tags.modelId = '{modelId}'" + targetCondition; - newConfiguration.Content.DeviceContent = desiredProperties; - newConfiguration.Priority = priority; - - try - { - _ = await this.registryManager.AddConfigurationAsync(newConfiguration); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to add configuration {newConfiguration.Id}", e); - } - } - - public async Task GetFailedDeploymentsCount() - { - try - { - var configurations = await this.registryManager.GetConfigurationsAsync(0); - - var failedDeploymentsCount= configurations.Where(c => c.Content.ModulesContent.Count > 0) - .Sum(c => c.SystemMetrics.Results.GetValueOrDefault("reportedFailedCount", 0)); - - return Convert.ToInt32(failedDeploymentsCount); - } - catch (RequestFailedException ex) - { - throw new InternalServerErrorException("Unable to get failed deployments count", ex); - } +// 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.Server.Services +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Azure; + using AzureIoTHub.Portal.Application.Helpers; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Crosscutting.Extensions; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10; + using Microsoft.Azure.Devices; + using Microsoft.Azure.Devices.Common.Extensions; + using Newtonsoft.Json.Linq; + + public class ConfigService : IConfigService + { + private readonly RegistryManager registryManager; + + public ConfigService( + RegistryManager registry) + { + this.registryManager = registry; + } + + public async Task> GetIoTEdgeConfigurations() + { + try + { + var configurations = await this.registryManager.GetConfigurationsAsync(0); + return configurations.Where(c => c.Content.ModulesContent.Count > 0); + } + catch (RequestFailedException ex) + { + throw new InternalServerErrorException("Unable to get IOT Edge configurations", ex); + } + } + + public async Task> GetDevicesConfigurations() + { + try + { + var configurations = await this.registryManager.GetConfigurationsAsync(0); + + return configurations + .Where(c => c.Priority > 0 && c.Content.ModulesContent.Count == 0); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException("Unable to get devices configurations", e); + } + } + + public async Task GetConfigItem(string id) + { + try + { + return await this.registryManager.GetConfigurationAsync(id); + } + catch (RequestFailedException ex) + { + throw new InternalServerErrorException($"Unable to get the configuration for id {id}", ex); + } + } + + public async Task> GetConfigModuleList(string modelId) + { + var configList = await GetIoTEdgeConfigurations(); + + var config = configList.FirstOrDefault((x) => x.Id.StartsWith(modelId, StringComparison.Ordinal)); + + if (config == null) + { + throw new InternalServerErrorException("Config does not exist."); + } + + var moduleList = new List(); + + // Details of every modules are stored within the EdgeAgent module data + if (config.Content.ModulesContent != null + && config.Content.ModulesContent.TryGetValue("$edgeAgent", out var edgeAgentModule) + && edgeAgentModule.TryGetValue("properties.desired", out var edgeAgentDesiredProperties)) + { + // Converts the object to a JObject to access its properties more easily + if (edgeAgentDesiredProperties is not JObject modObject) + { + throw new InvalidOperationException($"Could not parse properties.desired for the configuration id {config.Id}"); + } + + // Adds regular modules to the list of modules + if (modObject.TryGetValue("modules", out var modules)) + { + foreach (var newModule in modules.Values().Select(module => ConfigHelper.CreateGatewayModule(config, module))) + { + newModule.ModuleIdentityTwinSettings = ConfigHelper.CreateModuleTwinSettings(config.Content.ModulesContent, newModule.ModuleName); + moduleList.Add(newModule); + } + } + } + + return moduleList; + } + + public async Task> GetModelSystemModule(string modelId) + { + var configList = await GetIoTEdgeConfigurations(); + + var config = configList.FirstOrDefault((x) => x.Id.StartsWith(modelId, StringComparison.Ordinal)); + + if (config == null) + { + throw new InternalServerErrorException("Config does not exist."); + } + + var moduleList = new List(); + + // Details of every modules are stored within the EdgeAgent module data + if (config.Content.ModulesContent != null + && config.Content.ModulesContent.TryGetValue("$edgeAgent", out var edgeAgentModule) + && edgeAgentModule.TryGetValue("properties.desired", out var edgeAgentDesiredProperties)) + { + // Converts the object to a JObject to access its properties more easily + if (edgeAgentDesiredProperties is not JObject modObject) + { + throw new InvalidOperationException($"Could not parse properties.desired for the configuration id {config.Id}"); + } + + // Adds regular modules to the list of modules + if (modObject.TryGetValue("systemModules", out var modules)) + { + foreach (var newModule in modules.Values().Select(module => ConfigHelper.CreateGatewayModule(config, module))) + { + moduleList.Add(new EdgeModelSystemModule(newModule.ModuleName) + { + ImageUri = newModule.ImageURI, + EnvironmentVariables = newModule.EnvironmentVariables, + ContainerCreateOptions = newModule.ContainerCreateOptions, + }); + } + } + } + + return moduleList; + } + + public async Task> GetConfigRouteList(string modelId) + { + var configList = await GetIoTEdgeConfigurations(); + + var config = configList.FirstOrDefault((x) => x.Id.StartsWith(modelId, StringComparison.Ordinal)); + + if (config == null) + { + throw new InternalServerErrorException("Config does not exist."); + } + + var routeList = new List(); + + // Details of routes are stored within the EdgeHub properties.desired + if (config.Content.ModulesContent != null + && config.Content.ModulesContent.TryGetValue("$edgeHub", out var edgeHubModule) + && edgeHubModule.TryGetValue("properties.desired", out var edgeHubDesiredProperties)) + { + // + if (edgeHubDesiredProperties is not JObject modObject) + { + throw new InvalidOperationException($"Could not parse properties.desired for the configuration id {config.Id}"); + } + + // + if (modObject.TryGetValue("routes", out var routes)) + { + foreach (var newRoute in routes.Values().Select(route => ConfigHelper.CreateIoTEdgeRouteFromJProperty(route))) + { + routeList.Add(newRoute); + } + } + } + + return routeList; + + } + + public async Task DeleteConfiguration(string configId) + { + try + { + await this.registryManager.RemoveConfigurationAsync(configId); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException($"Unable to delete the configuration for id {configId}", e); + } + } + + public async Task RollOutDeviceModelConfiguration(string modelId, Dictionary desiredProperties) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + var configurationNamePrefix = modelId?.Trim() + .ToLowerInvariant() + .Replace(" ", "-", StringComparison.OrdinalIgnoreCase); +#pragma warning restore CA1308 // Normalize strings to uppercase + + await DeleteDeviceModelConfigurationByConfigurationNamePrefix(configurationNamePrefix); + + var newConfiguration = new Configuration($"{configurationNamePrefix}-{DateTime.UtcNow.Ticks}"); + + newConfiguration.Labels.Add("created-by", "Azure IoT hub Portal"); + newConfiguration.TargetCondition = $"tags.modelId = '{modelId}'"; + newConfiguration.Content.DeviceContent = desiredProperties; + + _ = await this.registryManager.AddConfigurationAsync(newConfiguration); + + return Guid.NewGuid().ToString(); + } + + public async Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix) + { + var configurations = await this.registryManager.GetConfigurationsAsync(0); + + foreach (var item in configurations) + { + if (!item.Id.StartsWith(configurationNamePrefix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + await this.registryManager.RemoveConfigurationAsync(item.Id); + } + } + + public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) + { + var configurations = await this.registryManager.GetConfigurationsAsync(0); + + var configurationNamePrefix = edgeModel.ModelId?.Trim() + .ToLowerInvariant() + .Replace(" ", "-", StringComparison.OrdinalIgnoreCase); + + var newConfiguration = new Configuration($"{configurationNamePrefix}-{DateTime.UtcNow.Ticks}"); + newConfiguration.Labels.Add("created-by", "Azure IoT hub Portal"); + newConfiguration.TargetCondition = $"tags.modelId = '{edgeModel.ModelId}'"; + newConfiguration.Priority = 10; + + newConfiguration.Content.ModulesContent = ConfigHelper.GenerateModulesContent(edgeModel); + + try + { + _ = await this.registryManager.AddConfigurationAsync(newConfiguration); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException("Unable to create configuration.", e); + } + + foreach (var item in configurations) + { + if (!item.Id.StartsWith(configurationNamePrefix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + await this.registryManager.RemoveConfigurationAsync(item.Id); + } + + return Guid.NewGuid().ToString(); + } + + public async Task RollOutDeviceConfiguration( + string modelId, + Dictionary desiredProperties, + string configurationId, + Dictionary targetTags, + int priority = 0) + { + IEnumerable configurations; + + try + { + configurations = await this.registryManager.GetConfigurationsAsync(0); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException("Unable to get configurations", e); + } + + var configurationNamePrefix = configurationId.Trim().ToLowerInvariant().RemoveDiacritics(); + + foreach (var item in configurations) + { + if (!item.Id.StartsWith(configurationNamePrefix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + try + { + await this.registryManager.RemoveConfigurationAsync(item.Id); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException($"Unable to remove configuration {item.Id}", e); + } + } + + var newConfiguration = new Configuration($"{configurationNamePrefix}-{DateTime.UtcNow.Ticks}"); + + newConfiguration.Labels.Add("created-by", "Azure IoT hub Portal"); + newConfiguration.Labels.Add("configuration-id", configurationId); + + var culture = CultureInfo.CreateSpecificCulture("en-En"); + var targetCondition = new StringBuilder(); + + foreach (var item in targetTags) + { + _ = targetCondition.AppendFormat(culture, " and tags.{0}", item.Key); + _ = targetCondition.AppendFormat(culture, " = '{0}'", item.Value); + } + + newConfiguration.TargetCondition = $"tags.modelId = '{modelId}'" + targetCondition; + newConfiguration.Content.DeviceContent = desiredProperties; + newConfiguration.Priority = priority; + + try + { + _ = await this.registryManager.AddConfigurationAsync(newConfiguration); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException($"Unable to add configuration {newConfiguration.Id}", e); + } + + return Guid.NewGuid().ToString(); + } + + public async Task GetFailedDeploymentsCount() + { + try + { + var configurations = await this.registryManager.GetConfigurationsAsync(0); + + var failedDeploymentsCount= configurations.Where(c => c.Content.ModulesContent.Count > 0) + .Sum(c => c.SystemMetrics.Results.GetValueOrDefault("reportedFailedCount", 0)); + + return Convert.ToInt32(failedDeploymentsCount); + } + catch (RequestFailedException ex) + { + throw new InternalServerErrorException("Unable to get failed deployments count", ex); + } } public Task> GetPublicEdgeModules() { return Task.FromResult>(Array.Empty()); } - } -} + } +} diff --git a/src/AzureIoTHub.Portal.Server/Services/DeviceConfigurationsService.cs b/src/AzureIoTHub.Portal.Server/Services/DeviceConfigurationsService.cs index fc1f7410a..5cbf5c93f 100644 --- a/src/AzureIoTHub.Portal.Server/Services/DeviceConfigurationsService.cs +++ b/src/AzureIoTHub.Portal.Server/Services/DeviceConfigurationsService.cs @@ -116,7 +116,7 @@ private async Task CreateOrUpdateConfiguration(DeviceConfig deviceConfig) desiredProperties.Add($"properties.desired.{item.Key}", propertyValue); } - await this.configService.RollOutDeviceConfiguration(deviceConfig.ModelId, desiredProperties, deviceConfig.ConfigurationId, deviceConfig.Tags, 100); + _ = await this.configService.RollOutDeviceConfiguration(deviceConfig.ModelId, desiredProperties, deviceConfig.ConfigurationId, deviceConfig.Tags, 100); } } } diff --git a/src/AzureIoTHub.Portal.Server/Services/DeviceModelService.cs b/src/AzureIoTHub.Portal.Server/Services/DeviceModelService.cs index 7583dc5c2..8023b9dff 100644 --- a/src/AzureIoTHub.Portal.Server/Services/DeviceModelService.cs +++ b/src/AzureIoTHub.Portal.Server/Services/DeviceModelService.cs @@ -205,7 +205,7 @@ private async Task CreateDeviceModelConfiguration(TModel deviceModel) _ = await this.deviceRegistryProvider.CreateEnrollmentGroupFromModelAsync(deviceModel.ModelId, deviceModel.Name, deviceModelTwin); - await this.configService.RollOutDeviceModelConfiguration(deviceModel.ModelId, desiredProperties); + _ = await this.configService.RollOutDeviceModelConfiguration(deviceModel.ModelId, desiredProperties); } } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs index 1c89f2728..feb4650c1 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs @@ -71,8 +71,10 @@ public void SetUp() [Test] public async Task CreateDeploymentWithComponentsAndExistingThingGroupAndThingTypeShouldCreateTheDeployment() - { - //Act + { + // Arrange + var deploymentId = Fixture.Create(); + _ = this.mockConfigHandler.Setup(handler => handler.AWSRegion).Returns("eu-west-1"); _ = this.mockConfigHandler.Setup(handler => handler.AWSAccountId).Returns("00000000"); @@ -94,7 +96,8 @@ public async Task CreateDeploymentWithComponentsAndExistingThingGroupAndThingTyp _ = this.mockGreengrasClient.Setup(s3 => s3.CreateDeploymentAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new CreateDeploymentResponse { - HttpStatusCode = HttpStatusCode.Created + HttpStatusCode = HttpStatusCode.Created, + DeploymentId = deploymentId }); _ = this.mockGreengrasClient.Setup(s3 => s3.DescribeComponentAsync(It.IsAny(), It.IsAny())) @@ -106,25 +109,19 @@ public async Task CreateDeploymentWithComponentsAndExistingThingGroupAndThingTyp HttpStatusCode = HttpStatusCode.Created }); - _ = this.mockEdgeModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync(edgeDeviceModelEntity); - - _ = this.mockEdgeModelRepository.Setup(repository => repository.Update(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); + // Act + var result = _ = await this.awsConfigService.RollOutEdgeModelConfiguration(edge); - //Arrange - await this.awsConfigService.RollOutEdgeModelConfiguration(edge); - - //Assert + // Assert + Assert.AreEqual(deploymentId, result); MockRepository.VerifyAll(); - } [Test] public async Task CreateDeploymentWithExistingComponentsAndExistingThingGroupAndThingTypeShouldCreateTheDeployment() { - //Act + // Arrange + var deploymentId = Fixture.Create(); _ = this.mockConfigHandler.Setup(handler => handler.AWSRegion).Returns("eu-west-1"); _ = this.mockConfigHandler.Setup(handler => handler.AWSAccountId).Returns("00000000"); @@ -146,7 +143,8 @@ public async Task CreateDeploymentWithExistingComponentsAndExistingThingGroupAnd _ = this.mockGreengrasClient.Setup(s3 => s3.CreateDeploymentAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new CreateDeploymentResponse { - HttpStatusCode = HttpStatusCode.Created + HttpStatusCode = HttpStatusCode.Created, + DeploymentId = deploymentId }); _ = this.mockGreengrasClient.Setup(s3 => s3.DescribeComponentAsync(It.IsAny(), It.IsAny())) @@ -155,25 +153,18 @@ public async Task CreateDeploymentWithExistingComponentsAndExistingThingGroupAnd HttpStatusCode = HttpStatusCode.OK }); - _ = this.mockEdgeModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync(edgeDeviceModelEntity); + // Act + var result = await this.awsConfigService.RollOutEdgeModelConfiguration(edge); - _ = this.mockEdgeModelRepository.Setup(repository => repository.Update(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - //Arrange - await this.awsConfigService.RollOutEdgeModelConfiguration(edge); - - //Assert + // Assert + Assert.AreEqual(deploymentId, result); MockRepository.VerifyAll(); - } [Test] public async Task CreateDeploymentWithNonExistingComponentsAndNonExistingThingGroupAndThingTypeShouldCreateThingGroupAndThingTypeAndTheDeployment() { - //Act + // Arrange _ = this.mockConfigHandler.Setup(handler => handler.AWSRegion).Returns("eu-west-1"); _ = this.mockConfigHandler.Setup(handler => handler.AWSAccountId).Returns("00000000"); @@ -209,19 +200,11 @@ public async Task CreateDeploymentWithNonExistingComponentsAndNonExistingThingGr HttpStatusCode = HttpStatusCode.Created }); - _ = this.mockEdgeModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync(edgeDeviceModelEntity); + // Act + _ = await this.awsConfigService.RollOutEdgeModelConfiguration(edge); - _ = this.mockEdgeModelRepository.Setup(repository => repository.Update(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - //Arrange - await this.awsConfigService.RollOutEdgeModelConfiguration(edge); - - //Assert + // Assert MockRepository.VerifyAll(); - } [Test] diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs index d599d7477..072854703 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs @@ -186,7 +186,7 @@ public async Task RolloutDeviceModelConfigurationStateUnderTestExpectedBehavior( .ReturnsAsync((Configuration conf) => conf); // Act - await configsServices.RollOutDeviceModelConfiguration(modelId, desiredProperties); + _ = await configsServices.RollOutDeviceModelConfiguration(modelId, desiredProperties); // Assert Assert.IsNotNull(newConfiguration); @@ -231,7 +231,7 @@ public async Task WhenConfigurationExistsRolloutDeviceModelConfigurationShouldRe .Returns(Task.CompletedTask); // Act - await configsServices.RollOutDeviceModelConfiguration(deviceType, desirectProperties); + _ = await configsServices.RollOutDeviceModelConfiguration(deviceType, desirectProperties); // Assert this.mockRegistryManager.Verify(c => c.GetConfigurationsAsync(It.IsAny()), Times.Once()); @@ -301,7 +301,7 @@ public async Task RolloutDeviceConfigurationStateUnderTestExpectedBehavior() .ReturnsAsync((Configuration conf) => conf); // Act - await configsServices.RollOutDeviceConfiguration(modelId, desiredProperties, configurationName, targetTags); + _ = await configsServices.RollOutDeviceConfiguration(modelId, desiredProperties, configurationName, targetTags); // Assert Assert.IsNotNull(newConfiguration); @@ -438,7 +438,7 @@ public async Task WhenConfigurationExistsRolloutDeviceConfigurationShouldRemoveI .Returns(Task.CompletedTask); // Act - await configsServices.RollOutDeviceConfiguration(modelId, desiredProperties, configurationName, targetTags); + _ = await configsServices.RollOutDeviceConfiguration(modelId, desiredProperties, configurationName, targetTags); // Assert this.mockRegistryManager.Verify(c => c.GetConfigurationsAsync(It.IsAny()), Times.Once()); @@ -885,7 +885,7 @@ public async Task RollOutEdgeModelConfigurationShouldCreateNewConfiguration(stri .ReturnsAsync((Configuration conf) => conf); // Act - await configsServices.RollOutEdgeModelConfiguration(edgeModel); + _ = await configsServices.RollOutEdgeModelConfiguration(edgeModel); // Assert Assert.IsNotNull(newConfiguration); @@ -929,7 +929,7 @@ public async Task WhenConfigurationExistsRollOutEdgeModelConfigurationShouldRemo .Returns(Task.CompletedTask); // Act - await configsServices.RollOutEdgeModelConfiguration(edgeModel); + _ = await configsServices.RollOutEdgeModelConfiguration(edgeModel); // Assert this.mockRegistryManager.Verify(c => c.GetConfigurationsAsync(It.IsAny()), Times.Once()); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs index 8313abed5..900a290c3 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs @@ -7,11 +7,13 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using AutoFixture; using Azure; using AzureIoTHub.Portal.Application.Services; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Services; + using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; using FluentAssertions; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Azure.Devices; @@ -22,7 +24,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using Configuration = Microsoft.Azure.Devices.Configuration; [TestFixture] - public class DeviceConfigurationsServiceTest + public class DeviceConfigurationsServiceTest : BackendUnitTest { private MockRepository mockRepository; @@ -170,7 +172,7 @@ public async Task CreateConfigStateUnderTestExpectedBehavior() It.Is(x => x == deviceConfig.ConfigurationId), It.IsAny>(), It.Is(x => x == 100))) - .Returns(Task.CompletedTask); + .Returns(Task.FromResult(Fixture.Create())); _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) .ReturnsAsync(new[] @@ -230,7 +232,7 @@ public async Task UpdateConfigStateUnderTestExpectedBehavior() It.Is(x => x == deviceConfig.ConfigurationId), It.IsAny>(), It.Is(x => x == 100))) - .Returns(Task.CompletedTask); + .Returns(Task.FromResult(Fixture.Create())); _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) .ReturnsAsync(new[] @@ -363,7 +365,7 @@ public async Task UpdateConfigShouldUpdatePropertyInValueType(DevicePropertyType It.Is(x => x == deviceConfig.ConfigurationId), It.IsAny>(), It.Is(x => x == 100))) - .Returns(Task.CompletedTask) + .Returns(Task.FromResult(Fixture.Create())) .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); @@ -422,7 +424,7 @@ public async Task CreateConfigShouldUpdatePropertyInValueType(DevicePropertyType It.Is(x => x == deviceConfig.ConfigurationId), It.IsAny>(), It.Is(x => x == 100))) - .Returns(Task.CompletedTask) + .Returns(Task.FromResult(Fixture.Create())) .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); @@ -472,7 +474,7 @@ public async Task WhenPropertyNotPresentInModelUpdateConfigShouldNotUpdateThePro It.Is(x => x == deviceConfig.ConfigurationId), It.IsAny>(), It.Is(x => x == 100))) - .Returns(Task.CompletedTask) + .Returns(Task.FromResult(Fixture.Create())) .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); @@ -523,7 +525,7 @@ public async Task WhenPropertyNotPresentInModelCreateConfigShouldNotUpdateThePro It.Is(x => x == deviceConfig.ConfigurationId), It.IsAny>(), It.Is(x => x == 100))) - .Returns(Task.CompletedTask) + .Returns(Task.FromResult(Fixture.Create())) .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs index 7bfc8a023..f9c91fcb8 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs @@ -188,7 +188,7 @@ public async Task CreateDeviceModelShouldCreateDeviceModel() _ = this.mockConfigService.Setup(service => service.RollOutDeviceModelConfiguration(deviceModelDto.ModelId, It.IsAny>())) - .Returns(Task.CompletedTask); + .Returns(Task.FromResult(Fixture.Create())); _ = this.mockDeviceModelImageManager.Setup(manager => manager.SetDefaultImageToModel(deviceModelDto.ModelId)) @@ -257,7 +257,7 @@ public async Task UpdateDeviceModelShouldUpdateDeviceModel() _ = this.mockConfigService.Setup(service => service.RollOutDeviceModelConfiguration(deviceModelDto.ModelId, It.IsAny>())) - .Returns(Task.CompletedTask); + .Returns(Task.FromResult(Fixture.Create())); // Act await this.deviceModelService.UpdateDeviceModel(deviceModelDto); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index 0717ca6f0..60fcacc04 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -1,625 +1,625 @@ -// 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.Server.Services -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Linq.Expressions; - using System.Threading.Tasks; - using AutoFixture; - using AutoMapper; - using AzureIoTHub.Portal.Application.Managers; - using AzureIoTHub.Portal.Application.Services; - using AzureIoTHub.Portal.Domain; - using AzureIoTHub.Portal.Domain.Entities; - using AzureIoTHub.Portal.Domain.Exceptions; - using AzureIoTHub.Portal.Domain.Repositories; - using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Shared.Models.v10.Filters; - using AzureIoTHub.Portal.Shared.Models.v10; - using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; - using FluentAssertions; - using Microsoft.AspNetCore.Http; - using Microsoft.Azure.Devices; - using Microsoft.EntityFrameworkCore; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using NUnit.Framework; - using AzureIoTHub.Portal.Infrastructure.Services; - - [TestFixture] - public class EdgeModelServiceTest : BackendUnitTest - { - private Mock mockUnitOfWork; - private Mock mockEdgeDeviceModelRepository; - private Mock mockEdgeDeviceModelCommandRepository; - private Mock mockConfigService; - private Mock mockDeviceModelImageManager; - private Mock mockLabelRepository; - private Mock mockConfigHandler; - - private IEdgeModelService edgeDeviceModelService; - - [SetUp] - public void SetUp() - { - base.Setup(); - - this.mockUnitOfWork = MockRepository.Create(); - this.mockEdgeDeviceModelRepository = MockRepository.Create(); - - this.mockEdgeDeviceModelCommandRepository = MockRepository.Create(); - this.mockConfigService = MockRepository.Create(); - this.mockDeviceModelImageManager = MockRepository.Create(); - this.mockLabelRepository = MockRepository.Create(); - this.mockConfigHandler = MockRepository.Create(); - - _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); - _ = ServiceCollection.AddSingleton(this.mockEdgeDeviceModelRepository.Object); - _ = ServiceCollection.AddSingleton(this.mockEdgeDeviceModelCommandRepository.Object); - _ = ServiceCollection.AddSingleton(this.mockConfigService.Object); - _ = ServiceCollection.AddSingleton(this.mockDeviceModelImageManager.Object); - _ = ServiceCollection.AddSingleton(this.mockLabelRepository.Object); - _ = ServiceCollection.AddSingleton(); - _ = ServiceCollection.AddSingleton(this.mockConfigHandler.Object); - - Services = ServiceCollection.BuildServiceProvider(); - - this.edgeDeviceModelService = Services.GetRequiredService(); - Mapper = Services.GetRequiredService(); - - } - - [Test] - public async Task GetEdgeModelsShouldReturnAList() - { - // Arrange - var expectedEdgeDeviceModels = Fixture.CreateMany(3).ToList(); - var expectedIoTEdgeDeviceModelListItems = expectedEdgeDeviceModels.Select(model => Mapper.Map(model)).ToList(); - var expectedImageUri = Fixture.Create(); - - foreach (var item in expectedIoTEdgeDeviceModelListItems) - { - item.ImageUrl = expectedImageUri; - } - - var edgeModelFilter = new EdgeModelFilter - { - Keyword = Guid.NewGuid().ToString() - }; - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetAllAsync(It.IsAny>>(), default, new Expression>[] { d => d.Labels })) - .ReturnsAsync(expectedEdgeDeviceModels); - - _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) - .Returns(expectedImageUri); - - // Act - var result = await this.edgeDeviceModelService.GetEdgeModels(edgeModelFilter); - - - // Assert - _ = result.Should().BeEquivalentTo(expectedIoTEdgeDeviceModelListItems); - MockRepository.VerifyAll(); - } - - [Test] - public async Task GetEdgeModelShouldReturnValueAsync() - { - // Arrange - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); - - var expectedModules = Fixture.CreateMany(2).ToList(); - var expectedRoutes = Fixture.CreateMany(2).ToList(); - var expectedSysModule = Fixture.CreateMany(2).ToList(); - var expectedImageUri = Fixture.Create(); - - var expectedEdgeDeviceModel = new IoTEdgeModel() - { - ModelId = Guid.NewGuid().ToString(), - Name = Guid.NewGuid().ToString(), - ImageUrl = expectedImageUri, - Description = Guid.NewGuid().ToString(), - EdgeModules = expectedModules, - EdgeRoutes = expectedRoutes, - SystemModules = expectedSysModule - }; - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId; - command.ModuleName = expectedModules[0].ModuleName; - return command; - }) .ToList(); - - expectedCommands.Add(new EdgeDeviceModelCommand - { - EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId, - Id = Guid.NewGuid().ToString(), - Name = Guid.NewGuid().ToString(), - ModuleName = Guid.NewGuid().ToString() - }); - - var expectedEdgeDeviceModelEntity = Mapper.Map(expectedEdgeDeviceModel); - - _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) - .Returns(expectedImageUri); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) - .ReturnsAsync(expectedEdgeDeviceModelEntity); - - _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(It.IsAny())) - .ReturnsAsync(expectedModules); - - _ = this.mockConfigService.Setup(x => x.GetModelSystemModule(It.IsAny())) - .ReturnsAsync(expectedSysModule); - - _ = this.mockConfigService.Setup(x => x.GetConfigRouteList(It.IsAny())) - .ReturnsAsync(expectedRoutes); - - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) - .Returns(expectedCommands); - - // Act - var result = await this.edgeDeviceModelService.GetEdgeModel(expectedEdgeDeviceModel.ModelId); - - // Assert - Assert.IsNotNull(result); - _ = result.Should().BeEquivalentTo(expectedEdgeDeviceModel); - } - - [Test] - public async Task GetEdgeModelForAwsShouldReturnValueAsync() - { - // Arrange - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); - - var expectedModules = Fixture.CreateMany(2).ToList(); - - var expectedImageUri = Fixture.Create(); - - var expectedEdgeDeviceModel = new IoTEdgeModel() - { - ModelId = Guid.NewGuid().ToString(), - Name = Guid.NewGuid().ToString(), - ImageUrl = expectedImageUri, - Description = Guid.NewGuid().ToString(), - EdgeModules = expectedModules - }; - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId; - command.ModuleName = expectedModules[0].ModuleName; - return command; - }) .ToList(); - - expectedCommands.Add(new EdgeDeviceModelCommand - { - EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId, - Id = Guid.NewGuid().ToString(), - Name = Guid.NewGuid().ToString(), - ModuleName = Guid.NewGuid().ToString() - }); - - var expectedEdgeDeviceModelEntity = Mapper.Map(expectedEdgeDeviceModel); - - _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) - .Returns(expectedImageUri); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) - .ReturnsAsync(expectedEdgeDeviceModelEntity); - - _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(It.IsAny())) - .ReturnsAsync(expectedModules); - - // Act - var result = await this.edgeDeviceModelService.GetEdgeModel(expectedEdgeDeviceModel.ModelId); - - // Assert - Assert.IsNotNull(result); - _ = result.Should().BeEquivalentTo(expectedEdgeDeviceModel); - } - - [Test] - public void GetEdgeModelShouldThrowResourceNotFoundExceptionIfModelDoesNotExist() - { - - // Arrange - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), m => m.Labels)) - .ReturnsAsync(value: null); - - // Act - var result = async () => await this.edgeDeviceModelService.GetEdgeModel("test"); - - // Assert - _ = result.Should().ThrowAsync(); - } - - [Test] - public async Task CreateEdgeModelForAzureShouldCreateEdgeModel() - { - // Arrange - - var edgeDeviceModel = Fixture.Create(); - var expectedImageUri = Fixture.Create(); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync((EdgeDeviceModel)null); - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = edgeDeviceModel.ModelId; - return command; - }) .ToList(); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) - .Returns(expectedCommands); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - - _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) - .Returns(Task.CompletedTask); - - _ = this.mockDeviceModelImageManager.Setup(manager => - manager.SetDefaultImageToModel(It.IsAny())) - .ReturnsAsync(expectedImageUri.ToString()); - - // Act - await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public async Task CreateEdgeModelForAwsShouldCreateEdgeModel() - { - // Arrange - - var edgeDeviceModel = Fixture.Create(); - var expectedImageUri = Fixture.Create(); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync((EdgeDeviceModel)null); - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); - - - _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) - .Returns(Task.CompletedTask); - - _ = this.mockDeviceModelImageManager.Setup(manager => - manager.SetDefaultImageToModel(It.IsAny())) - .ReturnsAsync(expectedImageUri.ToString()); - - // Act - await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public void CreateEdgeModelShouldThrowResourceAlreadyExistsExceptionIfModelAlreadyExists() - { - // Arrange - var edgeDeviceModel = Fixture.Create(); - var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync(edgeDeviceModelEntity); - - // Act - var result = async () => await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); - - // Assert - _ = result.Should().ThrowAsync(); - } - - [Test] - public void CreateEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs() - { - // Arrange - var edgeDeviceModel = Fixture.Create(); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync((EdgeDeviceModel)null); - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Throws(new DbUpdateException()); - - // Act - var result = async () => await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); - - // Assert - _ = result.Should().ThrowAsync(); - } - - [Test] - public async Task UpdateEdgeModelShouldUpdateEdgeModel() - { - // Arrange - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); - - var edgeDeviceModel = Fixture.Create(); - var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) - .ReturnsAsync(edgeDeviceModelEntity); - - this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) - .Verifiable(); - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Update(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = edgeDeviceModel.ModelId; - return command; - }) .ToList(); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) - .Returns(expectedCommands); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.InsertAsync(It.IsAny())) - .Returns(Task.CompletedTask); - - _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) - .Returns(Task.CompletedTask); - - // Act - await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); - - // Assert - MockRepository.VerifyAll(); - } - - - [Test] - public async Task UpdateEdgeModelForAWSShouldUpdateEdgeModel() - { - // Arrange - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); - - var edgeDeviceModel = Fixture.Create(); - var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) - .ReturnsAsync(edgeDeviceModelEntity); - - this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) - .Verifiable(); - - _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) +// 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.Server.Services +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + using AutoFixture; + using AutoMapper; + using AzureIoTHub.Portal.Application.Managers; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10.Filters; + using AzureIoTHub.Portal.Shared.Models.v10; + using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; + using FluentAssertions; + using Microsoft.AspNetCore.Http; + using Microsoft.Azure.Devices; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + using AzureIoTHub.Portal.Infrastructure.Services; + + [TestFixture] + public class EdgeModelServiceTest : BackendUnitTest + { + private Mock mockUnitOfWork; + private Mock mockEdgeDeviceModelRepository; + private Mock mockEdgeDeviceModelCommandRepository; + private Mock mockConfigService; + private Mock mockDeviceModelImageManager; + private Mock mockLabelRepository; + private Mock mockConfigHandler; + + private IEdgeModelService edgeDeviceModelService; + + [SetUp] + public void SetUp() + { + base.Setup(); + + this.mockUnitOfWork = MockRepository.Create(); + this.mockEdgeDeviceModelRepository = MockRepository.Create(); + + this.mockEdgeDeviceModelCommandRepository = MockRepository.Create(); + this.mockConfigService = MockRepository.Create(); + this.mockDeviceModelImageManager = MockRepository.Create(); + this.mockLabelRepository = MockRepository.Create(); + this.mockConfigHandler = MockRepository.Create(); + + _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); + _ = ServiceCollection.AddSingleton(this.mockEdgeDeviceModelRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockEdgeDeviceModelCommandRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockConfigService.Object); + _ = ServiceCollection.AddSingleton(this.mockDeviceModelImageManager.Object); + _ = ServiceCollection.AddSingleton(this.mockLabelRepository.Object); + _ = ServiceCollection.AddSingleton(); + _ = ServiceCollection.AddSingleton(this.mockConfigHandler.Object); + + Services = ServiceCollection.BuildServiceProvider(); + + this.edgeDeviceModelService = Services.GetRequiredService(); + Mapper = Services.GetRequiredService(); + + } + + [Test] + public async Task GetEdgeModelsShouldReturnAList() + { + // Arrange + var expectedEdgeDeviceModels = Fixture.CreateMany(3).ToList(); + var expectedIoTEdgeDeviceModelListItems = expectedEdgeDeviceModels.Select(model => Mapper.Map(model)).ToList(); + var expectedImageUri = Fixture.Create(); + + foreach (var item in expectedIoTEdgeDeviceModelListItems) + { + item.ImageUrl = expectedImageUri; + } + + var edgeModelFilter = new EdgeModelFilter + { + Keyword = Guid.NewGuid().ToString() + }; + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetAllAsync(It.IsAny>>(), default, new Expression>[] { d => d.Labels })) + .ReturnsAsync(expectedEdgeDeviceModels); + + _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) + .Returns(expectedImageUri); + + // Act + var result = await this.edgeDeviceModelService.GetEdgeModels(edgeModelFilter); + + + // Assert + _ = result.Should().BeEquivalentTo(expectedIoTEdgeDeviceModelListItems); + MockRepository.VerifyAll(); + } + + [Test] + public async Task GetEdgeModelShouldReturnValueAsync() + { + // Arrange + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); + + var expectedModules = Fixture.CreateMany(2).ToList(); + var expectedRoutes = Fixture.CreateMany(2).ToList(); + var expectedSysModule = Fixture.CreateMany(2).ToList(); + var expectedImageUri = Fixture.Create(); + + var expectedEdgeDeviceModel = new IoTEdgeModel() + { + ModelId = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + ImageUrl = expectedImageUri, + Description = Guid.NewGuid().ToString(), + EdgeModules = expectedModules, + EdgeRoutes = expectedRoutes, + SystemModules = expectedSysModule + }; + + var expectedCommands = Fixture.CreateMany(5).Select(command => + { + command.EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId; + command.ModuleName = expectedModules[0].ModuleName; + return command; + }) .ToList(); + + expectedCommands.Add(new EdgeDeviceModelCommand + { + EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId, + Id = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + ModuleName = Guid.NewGuid().ToString() + }); + + var expectedEdgeDeviceModelEntity = Mapper.Map(expectedEdgeDeviceModel); + + _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) + .Returns(expectedImageUri); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) + .ReturnsAsync(expectedEdgeDeviceModelEntity); + + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(It.IsAny())) + .ReturnsAsync(expectedModules); + + _ = this.mockConfigService.Setup(x => x.GetModelSystemModule(It.IsAny())) + .ReturnsAsync(expectedSysModule); + + _ = this.mockConfigService.Setup(x => x.GetConfigRouteList(It.IsAny())) + .ReturnsAsync(expectedRoutes); + + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) + .Returns(expectedCommands); + + // Act + var result = await this.edgeDeviceModelService.GetEdgeModel(expectedEdgeDeviceModel.ModelId); + + // Assert + Assert.IsNotNull(result); + _ = result.Should().BeEquivalentTo(expectedEdgeDeviceModel); + } + + [Test] + public async Task GetEdgeModelForAwsShouldReturnValueAsync() + { + // Arrange + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); + + var expectedModules = Fixture.CreateMany(2).ToList(); + + var expectedImageUri = Fixture.Create(); + + var expectedEdgeDeviceModel = new IoTEdgeModel() + { + ModelId = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + ImageUrl = expectedImageUri, + Description = Guid.NewGuid().ToString(), + EdgeModules = expectedModules + }; + + var expectedCommands = Fixture.CreateMany(5).Select(command => + { + command.EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId; + command.ModuleName = expectedModules[0].ModuleName; + return command; + }) .ToList(); + + expectedCommands.Add(new EdgeDeviceModelCommand + { + EdgeDeviceModelId = expectedEdgeDeviceModel.ModelId, + Id = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + ModuleName = Guid.NewGuid().ToString() + }); + + var expectedEdgeDeviceModelEntity = Mapper.Map(expectedEdgeDeviceModel); + + _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) + .Returns(expectedImageUri); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) + .ReturnsAsync(expectedEdgeDeviceModelEntity); + + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(It.IsAny())) + .ReturnsAsync(expectedModules); + + // Act + var result = await this.edgeDeviceModelService.GetEdgeModel(expectedEdgeDeviceModel.ModelId); + + // Assert + Assert.IsNotNull(result); + _ = result.Should().BeEquivalentTo(expectedEdgeDeviceModel); + } + + [Test] + public void GetEdgeModelShouldThrowResourceNotFoundExceptionIfModelDoesNotExist() + { + + // Arrange + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), m => m.Labels)) + .ReturnsAsync(value: null); + + // Act + var result = async () => await this.edgeDeviceModelService.GetEdgeModel("test"); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + public async Task CreateEdgeModelForAzureShouldCreateEdgeModel() + { + // Arrange + + var edgeDeviceModel = Fixture.Create(); + var expectedImageUri = Fixture.Create(); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync((EdgeDeviceModel)null); + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); + + var expectedCommands = Fixture.CreateMany(5).Select(command => + { + command.EdgeDeviceModelId = edgeDeviceModel.ModelId; + return command; + }) .ToList(); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) + .Returns(expectedCommands); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) + .Returns(Task.FromResult(Fixture.Create())); + + _ = this.mockDeviceModelImageManager.Setup(manager => + manager.SetDefaultImageToModel(It.IsAny())) + .ReturnsAsync(expectedImageUri.ToString()); + + // Act + await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task CreateEdgeModelForAwsShouldCreateEdgeModel() + { + // Arrange + + var edgeDeviceModel = Fixture.Create(); + var expectedImageUri = Fixture.Create(); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync((EdgeDeviceModel)null); + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); + + + _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) + .Returns(Task.FromResult(Fixture.Create())); + + _ = this.mockDeviceModelImageManager.Setup(manager => + manager.SetDefaultImageToModel(It.IsAny())) + .ReturnsAsync(expectedImageUri.ToString()); + + // Act + await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void CreateEdgeModelShouldThrowResourceAlreadyExistsExceptionIfModelAlreadyExists() + { + // Arrange + var edgeDeviceModel = Fixture.Create(); + var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(edgeDeviceModelEntity); + + // Act + var result = async () => await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + public void CreateEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs() + { + // Arrange + var edgeDeviceModel = Fixture.Create(); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync((EdgeDeviceModel)null); + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Throws(new DbUpdateException()); + + // Act + var result = async () => await this.edgeDeviceModelService.CreateEdgeModel(edgeDeviceModel); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + public async Task UpdateEdgeModelShouldUpdateEdgeModel() + { + // Arrange + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); + + var edgeDeviceModel = Fixture.Create(); + var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) + .ReturnsAsync(edgeDeviceModelEntity); + + this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) + .Verifiable(); + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Update(It.IsAny())); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + var expectedCommands = Fixture.CreateMany(5).Select(command => + { + command.EdgeDeviceModelId = edgeDeviceModel.ModelId; + return command; + }) .ToList(); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) + .Returns(expectedCommands); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) + .Returns(Task.FromResult(Fixture.Create())); + + // Act + await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); + + // Assert + MockRepository.VerifyAll(); + } + + + [Test] + public async Task UpdateEdgeModelForAWSShouldUpdateEdgeModel() + { + // Arrange + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); + + var edgeDeviceModel = Fixture.Create(); + var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), d => d.Labels)) + .ReturnsAsync(edgeDeviceModelEntity); + + this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) + .Verifiable(); + + _ = this.mockConfigService.Setup(x => x.RollOutEdgeModelConfiguration(It.IsAny())) + .Returns(Task.FromResult(Fixture.Create())); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void UpdateEdgeModelShouldThrowResourceNotFoundExceptionIfModelDoesNotExist() + { + // Arrange + var edgeDeviceModel = Fixture.Create(); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), m => m.Labels)) + .ReturnsAsync(value: null); + + // Act + var result = async () => await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + public void UpdateEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs() + { + // Arrange + var edgeDeviceModel = Fixture.Create(); + var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); + + _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(edgeDeviceModelEntity); + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Update(It.IsAny())); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Throws(new DbUpdateException()); + + // Act + var result = async () => await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + public async Task DeleteEdgeModelShouldDeleteEdgeModel() + { + // Arrange + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); + + var edgeDeviceModel = Fixture.Create(); + var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetByIdAsync(edgeDeviceModelEntity.Id, d => d.Labels)) + .ReturnsAsync(edgeDeviceModelEntity); + + this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) + .Verifiable(); + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Delete(It.IsAny())); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) .Returns(Task.CompletedTask); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - // Act - await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public void UpdateEdgeModelShouldThrowResourceNotFoundExceptionIfModelDoesNotExist() - { - // Arrange - var edgeDeviceModel = Fixture.Create(); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny(), m => m.Labels)) - .ReturnsAsync(value: null); - - // Act - var result = async () => await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); - - // Assert - _ = result.Should().ThrowAsync(); - } - - [Test] - public void UpdateEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs() - { - // Arrange - var edgeDeviceModel = Fixture.Create(); - var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); - - _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync(edgeDeviceModelEntity); - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Update(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Throws(new DbUpdateException()); - - // Act - var result = async () => await this.edgeDeviceModelService.UpdateEdgeModel(edgeDeviceModel); - - // Assert - _ = result.Should().ThrowAsync(); - } - - [Test] - public async Task DeleteEdgeModelShouldDeleteEdgeModel() - { - // Arrange - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("Azure"); - - var edgeDeviceModel = Fixture.Create(); - var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetByIdAsync(edgeDeviceModelEntity.Id, d => d.Labels)) - .ReturnsAsync(edgeDeviceModelEntity); - - this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) - .Verifiable(); - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Delete(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = edgeDeviceModel.ModelId; - return command; - }) .ToList(); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) - .Returns(expectedCommands); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); - - var configurations = new List() - { - new Configuration(edgeDeviceModel.ModelId) - }; - _ = this.mockConfigService.Setup(x => x.GetIoTEdgeConfigurations()) - .ReturnsAsync(configurations); - _ = this.mockConfigService.Setup(x => x.DeleteConfiguration(It.IsAny())) - .Returns(Task.CompletedTask); - - // Act - await this.edgeDeviceModelService.DeleteEdgeModel(edgeDeviceModel.ModelId); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public async Task DeleteEdgeModelForAwsShouldDeleteEdgeModel() - { - // Arrange - _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); - - var edgeDeviceModel = Fixture.Create(); - var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetByIdAsync(edgeDeviceModelEntity.Id, d => d.Labels)) - .ReturnsAsync(edgeDeviceModelEntity); - - this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) - .Verifiable(); - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Delete(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Returns(Task.CompletedTask); - - _ = this.mockConfigService.Setup(x => x.DeleteConfiguration(It.IsAny())) - .Returns(Task.CompletedTask); - - // Act - await this.edgeDeviceModelService.DeleteEdgeModel(edgeDeviceModel.ModelId); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public async Task DeleteEdgeModel_ModelDoesntExist_NothingIsDone() - { - // Arrange - var edgeModelId = Fixture.Create(); - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetByIdAsync(edgeModelId, d => d.Labels)) - .ReturnsAsync(value: null); - - // Act - await this.edgeDeviceModelService.DeleteEdgeModel(edgeModelId); - - // Assert - MockRepository.VerifyAll(); - } - - [Test] - public void DeleteEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs() - { - // Arrange - var edgeDeviceModel = Fixture.Create(); - - _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Delete(It.IsAny())); - _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) - .Throws(new DbUpdateException()); - - var expectedCommands = Fixture.CreateMany(5).Select(command => - { - command.EdgeDeviceModelId = edgeDeviceModel.ModelId; - return command; - }) .ToList(); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) - .Returns(expectedCommands); - _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); - - // Act - var result = async () => await this.edgeDeviceModelService.DeleteEdgeModel(edgeDeviceModel.ModelId); - - // Assert - _ = result.Should().ThrowAsync(); - } - - [Test] - public async Task GetEdgeDeviceModelAvatarShouldReturnEdgeDeviceModelAvatar() - { - // Arrange - var expectedImageUri = Fixture.Create(); - _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) - .Returns(expectedImageUri); - - // Act - var result = await this.edgeDeviceModelService.GetEdgeModelAvatar(Guid.NewGuid().ToString()); - - // Assert - _ = result.Should().Be(expectedImageUri.ToString()); - MockRepository.VerifyAll(); - } - - [Test] - public async Task UpdateEdgeDeviceModelAvatarShouldUpdateEdgeDeviceModelAvatar() - { - // Arrange - var expectedImageUri = Fixture.Create(); - - var mockFormFile = MockRepository.Create(); - - _ = this.mockDeviceModelImageManager.Setup(manager => - manager.ChangeDeviceModelImageAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(expectedImageUri.ToString()); - - _ = mockFormFile.Setup(file => file.OpenReadStream()) - .Returns(Stream.Null); - - // Act - var result = await this.edgeDeviceModelService.UpdateEdgeModelAvatar(Guid.NewGuid().ToString(), mockFormFile.Object); - - // Assert - _ = result.Should().Be(expectedImageUri.ToString()); - MockRepository.VerifyAll(); - } - - [Test] - public async Task DeleteEdgeDeviceModelAvatarShouldDeleteEdgeDeviceModelAvatar() - { - // Arrange - _ = this.mockDeviceModelImageManager - .Setup(manager => manager.DeleteDeviceModelImageAsync(It.IsAny())) - .Returns(Task.CompletedTask); - - // Act - await this.edgeDeviceModelService.DeleteEdgeModelAvatar(Guid.NewGuid().ToString()); - - // Assert - MockRepository.VerifyAll(); - } - } -} + var expectedCommands = Fixture.CreateMany(5).Select(command => + { + command.EdgeDeviceModelId = edgeDeviceModel.ModelId; + return command; + }) .ToList(); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) + .Returns(expectedCommands); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); + + var configurations = new List() + { + new Configuration(edgeDeviceModel.ModelId) + }; + _ = this.mockConfigService.Setup(x => x.GetIoTEdgeConfigurations()) + .ReturnsAsync(configurations); + _ = this.mockConfigService.Setup(x => x.DeleteConfiguration(It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await this.edgeDeviceModelService.DeleteEdgeModel(edgeDeviceModel.ModelId); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteEdgeModelForAwsShouldDeleteEdgeModel() + { + // Arrange + _ = this.mockConfigHandler.Setup(handler => handler.CloudProvider).Returns("AWS"); + + var edgeDeviceModel = Fixture.Create(); + var edgeDeviceModelEntity = Mapper.Map(edgeDeviceModel); + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetByIdAsync(edgeDeviceModelEntity.Id, d => d.Labels)) + .ReturnsAsync(edgeDeviceModelEntity); + + this.mockLabelRepository.Setup(repository => repository.Delete(It.IsAny())) + .Verifiable(); + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Delete(It.IsAny())); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + _ = this.mockConfigService.Setup(x => x.DeleteConfiguration(It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await this.edgeDeviceModelService.DeleteEdgeModel(edgeDeviceModel.ModelId); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteEdgeModel_ModelDoesntExist_NothingIsDone() + { + // Arrange + var edgeModelId = Fixture.Create(); + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.GetByIdAsync(edgeModelId, d => d.Labels)) + .ReturnsAsync(value: null); + + // Act + await this.edgeDeviceModelService.DeleteEdgeModel(edgeModelId); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void DeleteEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs() + { + // Arrange + var edgeDeviceModel = Fixture.Create(); + + _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.Delete(It.IsAny())); + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Throws(new DbUpdateException()); + + var expectedCommands = Fixture.CreateMany(5).Select(command => + { + command.EdgeDeviceModelId = edgeDeviceModel.ModelId; + return command; + }) .ToList(); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) + .Returns(expectedCommands); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.Delete(It.IsAny())); + + // Act + var result = async () => await this.edgeDeviceModelService.DeleteEdgeModel(edgeDeviceModel.ModelId); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + public async Task GetEdgeDeviceModelAvatarShouldReturnEdgeDeviceModelAvatar() + { + // Arrange + var expectedImageUri = Fixture.Create(); + _ = this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.IsAny())) + .Returns(expectedImageUri); + + // Act + var result = await this.edgeDeviceModelService.GetEdgeModelAvatar(Guid.NewGuid().ToString()); + + // Assert + _ = result.Should().Be(expectedImageUri.ToString()); + MockRepository.VerifyAll(); + } + + [Test] + public async Task UpdateEdgeDeviceModelAvatarShouldUpdateEdgeDeviceModelAvatar() + { + // Arrange + var expectedImageUri = Fixture.Create(); + + var mockFormFile = MockRepository.Create(); + + _ = this.mockDeviceModelImageManager.Setup(manager => + manager.ChangeDeviceModelImageAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(expectedImageUri.ToString()); + + _ = mockFormFile.Setup(file => file.OpenReadStream()) + .Returns(Stream.Null); + + // Act + var result = await this.edgeDeviceModelService.UpdateEdgeModelAvatar(Guid.NewGuid().ToString(), mockFormFile.Object); + + // Assert + _ = result.Should().Be(expectedImageUri.ToString()); + MockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteEdgeDeviceModelAvatarShouldDeleteEdgeDeviceModelAvatar() + { + // Arrange + _ = this.mockDeviceModelImageManager + .Setup(manager => manager.DeleteDeviceModelImageAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + await this.edgeDeviceModelService.DeleteEdgeModelAvatar(Guid.NewGuid().ToString()); + + // Assert + MockRepository.VerifyAll(); + } + } +} From 458efbbce6b780b4faa3947b497ab2303b08c2ee Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Fri, 2 Jun 2023 20:59:24 +0200 Subject: [PATCH 15/23] Add unit tests on GetPublicEdgeModules --- .../Services/AWS_Tests/AwsConfigTests.cs | 32 +++++++++++++++++++ .../Server/Services/ConfigServiceTests.cs | 14 ++++++++ 2 files changed, 46 insertions(+) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs index feb4650c1..d3aef3a2f 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Services/AWS_Tests/AwsConfigTests.cs @@ -28,6 +28,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Services.AWS_Tests using System.Text; using Newtonsoft.Json.Linq; using System.Linq; + using FluentAssertions; + using System; [TestFixture] public class AwsConfigTests : BackendUnitTest @@ -296,5 +298,35 @@ public async Task DeleteDeploymentShouldDeleteTheDeploymentVersionAndAllItsCompo MockRepository.VerifyAll(); } + + [Test] + public async Task GetPublicEdgeModules_AwsContext_EmptyListIsReturned() + { + //Arrange + var components = Fixture.CreateMany(2).ToList(); + + var expectedPublicEdgeModules = components.Select(c => new IoTEdgeModule + { + Id = c.Arn, + ModuleName = c.ComponentName, + Version = c.LatestVersion.ComponentVersion, + ImageURI = "example.com" + }).ToList(); + + _ = this.mockGreengrasClient.Setup(s3 => s3.ListComponentsAsync(It.Is(a => a.Scope == ComponentVisibilityScope.PUBLIC), It.IsAny())) + .ReturnsAsync(new ListComponentsResponse + { + Components = components, + NextToken = string.Empty, + }); + + // Act + var publicEdgeModules = await this.awsConfigService.GetPublicEdgeModules(); + + //Assert + _ = publicEdgeModules.Should().BeEquivalentTo(expectedPublicEdgeModules); + MockRepository.VerifyAll(); + + } } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs index 072854703..8044c6535 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs @@ -989,5 +989,19 @@ public async Task WhenConfigurationExistsDeleteDeviceModelConfigurationByConfigu this.mockRegistryManager.Verify(c => c.GetConfigurationsAsync(It.IsAny()), Times.Once()); this.mockRegistryManager.Verify(c => c.RemoveConfigurationAsync(It.IsAny()), Times.Once()); } + + [Test] + public async Task GetPublicEdgeModules_AzureContext_EmptyListIsReturned() + { + // Arrange + var configsServices = CreateConfigsServices(); + + //Arrange + var publicEdgeModules = await configsServices.GetPublicEdgeModules(); + + //Assert + _ = publicEdgeModules.Should().BeEquivalentTo(Array.Empty()); + this.mockRepository.VerifyAll(); + } } } From 6ae5671ec154953d8defdd2586d11c203ba8b8ac Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Fri, 2 Jun 2023 22:31:38 +0200 Subject: [PATCH 16/23] Fix codeql warning on CreateEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExceptionOccurs --- .../Server/Services/EdgeModelServiceTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index 60fcacc04..85c96a3fd 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -335,7 +335,7 @@ public void CreateEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExce var edgeDeviceModel = Fixture.Create(); _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync((EdgeDeviceModel)null); + .ReturnsAsync((EdgeDeviceModel)default); _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) .Returns(Task.CompletedTask); _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) From 159c93828cadeda681178866ecad8430c67e0ae9 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Sat, 3 Jun 2023 21:14:06 +0200 Subject: [PATCH 17/23] Rename AwsGreengrassPublicComponents to AwsGreengrassPublicComponentsDialog --- ...mponents.razor => AwsGreengrassPublicComponentsDialog.razor} | 0 .../Pages/EdgeModels/CreateEdgeModelsPage.razor | 2 +- .../Pages/EdgeModels/EdgeModelDetailPage.razor | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/{AwsGreengrassPublicComponents.razor => AwsGreengrassPublicComponentsDialog.razor} (100%) diff --git a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponentsDialog.razor similarity index 100% rename from src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponents.razor rename to src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassPublicComponentsDialog.razor diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor index c4c6a959a..850890f9e 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor @@ -316,7 +316,7 @@ DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; - await DialogService.Show(string.Empty, parameters, options).Result; + await DialogService.Show(string.Empty, parameters, options).Result; await InvokeAsync(StateHasChanged); } diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor index 4b2d7861e..7b77626a6 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor @@ -417,7 +417,7 @@ DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; - await DialogService.Show(string.Empty, parameters, options).Result; + await DialogService.Show(string.Empty, parameters, options).Result; await InvokeAsync(StateHasChanged); } From 696c4853f5e3aeb5ec65e30a0ce46850b11204ec Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Sat, 3 Jun 2023 21:59:44 +0200 Subject: [PATCH 18/23] Add unit tests on AwsGreengrassComponentDialog and AwsGreengrassPublicComponentsDialog --- .../AwsGreengrassComponentDialog.razor | 4 +- .../AwsGreengrassComponentDialogTests.cs | 148 ++++++++++++++++ ...wsGreengrassPublicComponentsDialogTests.cs | 161 ++++++++++++++++++ 3 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassComponentDialogTests.cs create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassPublicComponentsDialogTests.cs diff --git a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor index 073480b17..333a97277 100644 --- a/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor +++ b/src/AzureIoTHub.Portal.Client/Dialogs/EdgeModels/AwsGreengrassComponentDialog.razor @@ -3,9 +3,9 @@ - + - diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassComponentDialogTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassComponentDialogTests.cs new file mode 100644 index 000000000..0204fc82c --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassComponentDialogTests.cs @@ -0,0 +1,148 @@ +// 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.Client.Dialogs.EdgeModels +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Client.Dialogs.EdgeModels; + using AzureIoTHub.Portal.Client.Enums; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Bunit; + using FluentAssertions; + using Moq; + using MudBlazor; + using NUnit.Framework; + + [TestFixture] + public class AwsGreengrassComponentDialogTests : BlazorUnitTest + { + public override void Setup() + { + base.Setup(); + } + + [Test] + public async Task AwsGreengrassComponentDialog_ClickOnCancel_DialogCanceled() + { + // Arrange + var edgeModules = Array.Empty().ToList(); + + var cut = RenderComponent(); + var service = Services.GetService() as DialogService; + + var parameters = new DialogParameters + { + { "Context", Context.Create }, + { "EdgeModules", edgeModules } + }; + + IDialogReference dialogReference = null; + await cut.InvokeAsync(() => dialogReference = service?.Show(string.Empty, parameters)); + + // Act + cut.Find("#greengrass-component-cancel").Click(); + + var result = await dialogReference.Result; + + // Assert + _ = result.Canceled.Should().BeTrue(); + _ = edgeModules.Should().BeEmpty(); + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public async Task AwsGreengrassComponentDialog_CreateAWSComponentAndSubmit_EdgeModuleAdded() + { + // Arrange + var edgeModules = Array.Empty().ToList(); + + var inputJsonRecipe = /*lang=json*/ @" +{ + ""ComponentName"": ""com.example.DDboxAdvantech"", + ""ComponentVersion"": ""1.0.0"" +} +"; + + var cut = RenderComponent(); + var service = Services.GetService() as DialogService; + + var parameters = new DialogParameters + { + { "Context", Context.Create }, + { "EdgeModules", edgeModules } + }; + + IDialogReference dialogReference = null; + await cut.InvokeAsync(() => dialogReference = service?.Show(string.Empty, parameters)); + + // Act + cut.WaitForElement("#greengrass-component-recipe-json").Change(inputJsonRecipe); + cut.Find("#greengrass-component-submit").Click(); + + var result = await dialogReference.Result; + + // Assert + _ = result.Canceled.Should().BeFalse(); + _ = edgeModules.Count.Should().Be(1); + _ = edgeModules.First().ModuleName.Should().Be("com.example.DDboxAdvantech"); + _ = edgeModules.First().Version.Should().Be("1.0.0"); + _ = edgeModules.First().ContainerCreateOptions.Should().Be(inputJsonRecipe); + _ = edgeModules.First().ImageURI.Should().Be("example.com"); + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public async Task AwsGreengrassComponentDialog_EditAWSComponentAndSubmit_EdgeModuleUpdated() + { + // Arrange + var existingJsonRecipe = /*lang=json*/ @" +{ + ""ComponentName"": ""com.example.DDboxAdvantech"", + ""ComponentVersion"": ""1.0.0"" +} +"; + var edgeModule = new IoTEdgeModule + { + ModuleName = "com.example.DDboxAdvantech", + Version = "1.0.0", + ContainerCreateOptions = existingJsonRecipe + }; + + var newJsonRecipe = /*lang=json*/ @" +{ + ""ComponentName"": ""com.example.DDboxAdvantech"", + ""ComponentVersion"": ""2.0.0"" +} +"; + + var cut = RenderComponent(); + var service = Services.GetService() as DialogService; + + var parameters = new DialogParameters + { + { "Context", Context.Edit }, + { "Module", edgeModule } + }; + + IDialogReference dialogReference = null; + await cut.InvokeAsync(() => dialogReference = service?.Show(string.Empty, parameters)); + + // Act + cut.WaitForElement("#greengrass-component-recipe-json").Change(newJsonRecipe); + cut.Find("#greengrass-component-submit").Click(); + + var result = await dialogReference.Result; + + // Assert + _ = result.Canceled.Should().BeFalse(); + _ = edgeModule.ModuleName.Should().Be("com.example.DDboxAdvantech"); + _ = edgeModule.Version.Should().Be("2.0.0"); + _ = edgeModule.ContainerCreateOptions.Should().Be(newJsonRecipe); + _ = edgeModule.ImageURI.Should().Be("example.com"); + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassPublicComponentsDialogTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassPublicComponentsDialogTests.cs new file mode 100644 index 000000000..684b75ed7 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Dialogs/EdgeModels/AwsGreengrassPublicComponentsDialogTests.cs @@ -0,0 +1,161 @@ +// 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.Client.Dialogs.EdgeModels +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AutoFixture; + using AzureIoTHub.Portal.Client.Dialogs.EdgeModels; + using AzureIoTHub.Portal.Client.Services; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Bunit; + using FluentAssertions; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using MudBlazor; + using NUnit.Framework; + + [TestFixture] + public class AwsGreengrassPublicComponentsDialogTests : BlazorUnitTest + { + private Mock mockEdgeModelClientService; + + public override void Setup() + { + base.Setup(); + + this.mockEdgeModelClientService = MockRepository.Create(); + + _ = Services.AddSingleton(this.mockEdgeModelClientService.Object); + } + + [Test] + public async Task AwsGreengrassPublicComponentsDialog_AfterOnInitializedAsync_PublicEdgeComponentsAreLoaded() + { + // Arrange + var edgeModules = Array.Empty().ToList(); + var publicEdgeComponents = Fixture.CreateMany(10).ToList(); + + _ = this.mockEdgeModelClientService.Setup(s => s.GetPublicEdgeModules()) + .ReturnsAsync(publicEdgeComponents); + + var cut = RenderComponent(); + var service = Services.GetService() as DialogService; + + var parameters = new DialogParameters + { + {"EdgeModules", edgeModules} + }; + + // Act + await cut.InvokeAsync(() => service?.Show(string.Empty, parameters)); + cut.WaitForAssertion(() => cut.FindAll("table tbody tr").Count.Should().Be(10)); + + // Assert + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public async Task AwsGreengrassPublicComponentsDialog_ClickOnCancel_DialogCanceled() + { + // Arrange + var edgeModules = Array.Empty().ToList(); + var publicEdgeComponents = Fixture.CreateMany(10).ToList(); + + _ = this.mockEdgeModelClientService.Setup(s => s.GetPublicEdgeModules()) + .ReturnsAsync(publicEdgeComponents); + + var cut = RenderComponent(); + var service = Services.GetService() as DialogService; + + var parameters = new DialogParameters + { + {"EdgeModules", edgeModules} + }; + + IDialogReference dialogReference = null; + await cut.InvokeAsync(() => dialogReference = service?.Show(string.Empty, parameters)); + cut.WaitForAssertion(() => cut.FindAll("table tbody tr").Count.Should().Be(10)); + + // Act + cut.Find("#greengrass-public-components-cancel").Click(); + + var result = await dialogReference.Result; + + // Assert + _ = result.Canceled.Should().BeTrue(); + _ = edgeModules.Should().BeEmpty(); + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public async Task AwsGreengrassPublicComponentsDialog_ClickOnSubmitWithoutSelectingPublicComponents_EdgeModulesNotUpdated() + { + // Arrange + var edgeModules = Array.Empty().ToList(); + var publicEdgeComponents = Fixture.CreateMany(10).ToList(); + + _ = this.mockEdgeModelClientService.Setup(s => s.GetPublicEdgeModules()) + .ReturnsAsync(publicEdgeComponents); + + var cut = RenderComponent(); + var service = Services.GetService() as DialogService; + + var parameters = new DialogParameters + { + {"EdgeModules", edgeModules} + }; + + IDialogReference dialogReference = null; + await cut.InvokeAsync(() => dialogReference = service?.Show(string.Empty, parameters)); + cut.WaitForAssertion(() => cut.FindAll("table tbody tr").Count.Should().Be(10)); + + // Act + cut.Find("#greengrass-public-components-submit").Click(); + + var result = await dialogReference.Result; + + // Assert + _ = result.Canceled.Should().BeFalse(); + _ = edgeModules.Should().BeEmpty(); + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public async Task AwsGreengrassPublicComponentsDialog_ClickOnSubmitAfterSelectingPublicComponent_EdgeModulesNotUpdated() + { + // Arrange + var edgeModules = Array.Empty().ToList(); + var publicEdgeComponents = Fixture.CreateMany(10).ToList(); + + _ = this.mockEdgeModelClientService.Setup(s => s.GetPublicEdgeModules()) + .ReturnsAsync(publicEdgeComponents); + + var cut = RenderComponent(); + var service = Services.GetService() as DialogService; + + var parameters = new DialogParameters + { + {"EdgeModules", edgeModules} + }; + + IDialogReference dialogReference = null; + await cut.InvokeAsync(() => dialogReference = service?.Show(string.Empty, parameters)); + cut.WaitForAssertion(() => cut.FindAll("table tbody tr").Count.Should().Be(10)); + + // Act + cut.WaitForAssertion(() => cut.Find("table tbody tr").Click()); + cut.Find("#greengrass-public-components-submit").Click(); + + var result = await dialogReference.Result; + + // Assert + _ = result.Canceled.Should().BeFalse(); + _ = edgeModules.Count.Should().Be(1); + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + } +} From 4958fb3e5dd61a110b2ca0d41ba2e18709d24ba5 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Sat, 3 Jun 2023 22:09:24 +0200 Subject: [PATCH 19/23] Add UT GetPublicEdgeModules_GetPublicEdgeModules_EdgeModulesReturned --- .../v1.0/EdgeModelsControllerTest.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs index 78a82ea42..6690184d8 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs @@ -256,5 +256,46 @@ public async Task DeleteAvatarShouldDeleteAvatar() this.mockRepository.VerifyAll(); } + [Test] + public async Task GetPublicEdgeModules_GetPublicEdgeModules_EdgeModulesReturned() + { + // Arrange + var edgeModelController = CreateController(); + var publicEdgeModules = new List + { + new IoTEdgeModule{ + Id = Guid.NewGuid().ToString() + } + }; + + _ = this.mockEdgeModelService + .Setup(x => x.GetPublicEdgeModules()) + .ReturnsAsync(publicEdgeModules); + + // Act + var response = await edgeModelController.GetPublicEdgeModules(); + + // Assert + Assert.IsNotNull(response); + Assert.IsAssignableFrom(response.Result); + + if (response.Result is OkObjectResult okResponse) + { + Assert.AreEqual(200, okResponse.StatusCode); + + Assert.IsNotNull(okResponse.Value); + if (okResponse.Value is List result) + { + Assert.AreEqual(1, result.Count); + } + } + else + { + Assert.Fail("Cannot inspect the result."); + } + + this.mockRepository.VerifyAll(); + } + } } From 9b7f91f52f2dc223520bee13a8f4280481025451 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Sat, 3 Jun 2023 22:13:28 +0200 Subject: [PATCH 20/23] Add UT GetPublicEdgeModules_GetPublicEdgeModules_EdgeModulesReturned on EdgeModelService --- .../Server/Services/EdgeModelServiceTest.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index 85c96a3fd..cc43ad55a 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -621,5 +621,23 @@ public async Task DeleteEdgeDeviceModelAvatarShouldDeleteEdgeDeviceModelAvatar() // Assert MockRepository.VerifyAll(); } + + [Test] + public async Task GetPublicEdgeModules_GetPublicEdgeModules_EdgeModulesReturned() + { + // Arrange + var edgeModules = Fixture.CreateMany(10).ToList(); + + _ = this.mockConfigService + .Setup(s => s.GetPublicEdgeModules()) + .ReturnsAsync(edgeModules); + + // Act + var result = await this.edgeDeviceModelService.GetPublicEdgeModules(); + + // Assert + _ = result.Should().BeEquivalentTo(edgeModules); + MockRepository.VerifyAll(); + } } } From f0484533aae40df130ac2d62d7c5d2f979c4fc6a Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Sat, 3 Jun 2023 22:17:05 +0200 Subject: [PATCH 21/23] Add UT GetPublicEdgeModules_GetPublicEdgeModules_EdgeModulesReturned on EdgeModelClientService --- .../Services/EdgeModelClientServiceTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs index 80c42e484..b48e451d9 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs @@ -200,5 +200,22 @@ public async Task DeleteAvatarPropertiesShouldChangeAvatar() MockHttpClient.VerifyNoOutstandingRequest(); MockHttpClient.VerifyNoOutstandingExpectation(); } + + [Test] + public async Task GetPublicEdgeModules_GetPublicEdgeModules_EdgeModulesReturned() + { + var expectedModels = Fixture.Build().CreateMany(3).ToList(); + + _ = MockHttpClient.When(HttpMethod.Get, "/api/edge/models/public-modules") + .RespondJson(expectedModels); + + // Act + var result = await this.edgeModelClientService.GetPublicEdgeModules(); + + // Assert + _ = result.Should().BeEquivalentTo(expectedModels); + MockHttpClient.VerifyNoOutstandingRequest(); + MockHttpClient.VerifyNoOutstandingExpectation(); + } } } From dc06a7e17e921053bbf94ed9dcf64226f0f92037 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Sat, 3 Jun 2023 22:40:18 +0200 Subject: [PATCH 22/23] Add unit tests on EdgeModelDetailPage and CreateEdgeModelsPage on add edge module and public edge modules --- .../EdgeModels/CreateEdgeModelsPage.razor | 4 +- .../EdgeModels/EdgeModelDetailPage.razor | 4 +- .../EdgeModels/CreateEdgeModelsPageTest.cs | 61 ++++++++++++++++++- .../EdgeModels/EdgeModelDetailPageTest.cs | 60 +++++++++++++++--- .../Services/EdgeModelClientServiceTest.cs | 3 +- 5 files changed, 118 insertions(+), 14 deletions(-) diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor index 850890f9e..558e15ec4 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/CreateEdgeModelsPage.razor @@ -218,14 +218,14 @@ @moduleContext.ModuleName - Detail + Detail - Add new module + Add new module Add public modules diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor index 7b77626a6..342939f02 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor @@ -225,14 +225,14 @@ @moduleContext.ModuleName - Detail + Detail - Add new module + Add new module Add public modules diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/CreateEdgeModelsPageTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/CreateEdgeModelsPageTest.cs index 001b26a42..0a918ec59 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/CreateEdgeModelsPageTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/CreateEdgeModelsPageTest.cs @@ -4,7 +4,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Client.Pages.EdgeModels { using System; - using System.Threading.Tasks; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Client.Dialogs.EdgeModels; using AzureIoTHub.Portal.Client.Exceptions; using AzureIoTHub.Portal.Client.Models; using AzureIoTHub.Portal.Client.Pages.EdgeModels; @@ -378,5 +379,63 @@ public void ClickOnDeleteRouteShouldRemoveRouteFromEdgeModelData() cut.WaitForAssertion(() => Assert.AreEqual(0, cut.FindAll(".deleteRouteButton").Count)); cut.WaitForAssertion(() => MockRepository.VerifyAll()); } + + [Test] + public void CreateEdgeModelsPage_ClickOnAddEdgeModule_ShowAwsGreengrassComponentDialog() + { + // Arrange + _ = Services.AddSingleton(new PortalSettings { CloudProvider = "AWS" }); + + var edgeModel = new IoTEdgeModel() + { + Name = Guid.NewGuid().ToString(), + }; + + var mockDialogReference = MockRepository.Create(); + _ = mockDialogReference.Setup(c => c.Result).ReturnsAsync(DialogResult.Ok("Ok")); + + _ = this.mockDialogService + .Setup(c => c.Show(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(mockDialogReference.Object); + + var cut = RenderComponent(); + + cut.WaitForElement($"#{nameof(IoTEdgeModel.Name)}").Change(edgeModel.Name); + + // Act + cut.WaitForElement("#add-edge-module").Click(); + + // Assert + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public void CreateEdgeModelsPage_ClickOnAddPublicEdgeModules_ShowAwsGreengrassPublicComponentsDialog() + { + // Arrange + _ = Services.AddSingleton(new PortalSettings { CloudProvider = "AWS" }); + + var edgeModel = new IoTEdgeModel() + { + Name = Guid.NewGuid().ToString(), + }; + + var mockDialogReference = MockRepository.Create(); + _ = mockDialogReference.Setup(c => c.Result).ReturnsAsync(DialogResult.Ok("Ok")); + + _ = this.mockDialogService + .Setup(c => c.Show(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(mockDialogReference.Object); + + var cut = RenderComponent(); + + cut.WaitForElement($"#{nameof(IoTEdgeModel.Name)}").Change(edgeModel.Name); + + // Act + cut.WaitForElement("#add-public-edge-modules").Click(); + + // Assert + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs index 8ca91684a..8f0e7de63 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs @@ -5,7 +5,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Client.Pages.EdgeModels { using System; using System.Collections.Generic; - using System.Threading.Tasks; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Client.Dialogs.EdgeModels; using AzureIoTHub.Portal.Client.Exceptions; using AzureIoTHub.Portal.Client.Models; using AzureIoTHub.Portal.Client.Pages.EdgeModels; @@ -31,8 +32,6 @@ public class EdgeModelDetailPageTest : BlazorUnitTest private readonly string mockEdgeModleId = Guid.NewGuid().ToString(); - private FakeNavigationManager mockNavigationManager; - public override void Setup() { base.Setup(); @@ -45,9 +44,6 @@ public override void Setup() _ = Services.AddSingleton(this.mockDialogService.Object); _ = Services.AddSingleton(this.mockSnackbarService.Object); _ = Services.AddSingleton(new PortalSettings { CloudProvider = "Azure" }); - - - this.mockNavigationManager = Services.GetRequiredService(); } [Test] @@ -63,7 +59,7 @@ public void ClickOnReturnButtonMustNavigateToPreviousPage() cut.WaitForElement("#returnButton").Click(); // Assert - cut.WaitForAssertion(() => this.mockNavigationManager.Uri.Should().EndWith("/edge/models")); + cut.WaitForAssertion(() => Services.GetRequiredService().Uri.Should().EndWith("/edge/models")); cut.WaitForAssertion(() => MockRepository.VerifyAll()); } @@ -225,7 +221,7 @@ public void ClickOnDeleteEdgeModelButtonShouldShowDeleteDialogAndRedirectIfOk() deleteModelBtn.Click(); // Assert - cut.WaitForAssertion(() => this.mockNavigationManager.Uri.Should().EndWith("/edge/models")); + cut.WaitForAssertion(() => Services.GetRequiredService().Uri.Should().EndWith("/edge/models")); cut.WaitForAssertion(() => MockRepository.VerifyAll()); } @@ -457,5 +453,53 @@ public void ClickOnDeleteRouteShouldRemoveRouteFromEdgeModelData() cut.WaitForAssertion(() => Assert.AreEqual(0, cut.FindAll(".deleteRouteButton").Count)); cut.WaitForAssertion(() => MockRepository.VerifyAll()); } + + [Test] + public void EdgeModelDetailPage_ClickOnAddEdgeModule_ShowAwsGreengrassComponentDialog() + { + // Arrange + _ = Services.AddSingleton(new PortalSettings { CloudProvider = "AWS" }); + + _ = SetupLoadEdgeModel(); + + var mockDialogReference = MockRepository.Create(); + _ = mockDialogReference.Setup(c => c.Result).ReturnsAsync(DialogResult.Ok("Ok")); + + _ = this.mockDialogService + .Setup(c => c.Show(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(mockDialogReference.Object); + + var cut = RenderComponent(ComponentParameter.CreateParameter("ModelID", this.mockEdgeModleId)); + + // Act + cut.WaitForElement("#add-edge-module").Click(); + + // Assert + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public void EdgeModelDetailPage_ClickOnAddPublicEdgeModules_ShowAwsGreengrassPublicComponentsDialog() + { + // Arrange + _ = Services.AddSingleton(new PortalSettings { CloudProvider = "AWS" }); + + _ = SetupLoadEdgeModel(); + + var mockDialogReference = MockRepository.Create(); + _ = mockDialogReference.Setup(c => c.Result).ReturnsAsync(DialogResult.Ok("Ok")); + + _ = this.mockDialogService + .Setup(c => c.Show(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(mockDialogReference.Object); + + var cut = RenderComponent(ComponentParameter.CreateParameter("ModelID", this.mockEdgeModleId)); + + // Act + cut.WaitForElement("#add-public-edge-modules").Click(); + + // Assert + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs index b48e451d9..2e4019ae8 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/EdgeModelClientServiceTest.cs @@ -203,7 +203,8 @@ public async Task DeleteAvatarPropertiesShouldChangeAvatar() [Test] public async Task GetPublicEdgeModules_GetPublicEdgeModules_EdgeModulesReturned() - { + { + // Arrange var expectedModels = Fixture.Build().CreateMany(3).ToList(); _ = MockHttpClient.When(HttpMethod.Get, "/api/edge/models/public-modules") From fe2d123aa61458d28f538ac8cf2208dd0b6d5cbb Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf Date: Sat, 3 Jun 2023 22:47:47 +0200 Subject: [PATCH 23/23] Fix codeql warning --- .../Server/Services/EdgeModelServiceTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index cc43ad55a..1e6aeb8b8 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -335,7 +335,7 @@ public void CreateEdgeModelShouldThrowInternalServerErrorExceptionIfDbUpdateExce var edgeDeviceModel = Fixture.Create(); _ = this.mockEdgeDeviceModelRepository.Setup(x => x.GetByIdAsync(It.IsAny())) - .ReturnsAsync((EdgeDeviceModel)default); + .ReturnsAsync(value: null); _ = this.mockEdgeDeviceModelRepository.Setup(repository => repository.InsertAsync(It.IsAny())) .Returns(Task.CompletedTask); _ = this.mockUnitOfWork.Setup(work => work.SaveAsync())