From 7d1c06741cf71ef2159f46bfccf5105cea4f3de3 Mon Sep 17 00:00:00 2001 From: ssgueye2 <127868584+ssgueye2@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:13:34 +0200 Subject: [PATCH] #2169 improve aws exceptions management (#2207) * Improve AWS exceptions (1) * improve AWS exceptions (2) * delete using * Improve AWS exceptions (3) * delete unused using instructions * fix some code scanning warning * fixed * Failed tests to fix * Improve AWS Exception * Fix some warnings * Update AWSEdgeDevicesService.cs * DeleteDynamicGroups when deleting thing type --- .../Jobs/AWS/SyncGreenGrassDeploymentsJob.cs | 47 +- .../Jobs/AWS/SyncThingTypesJob.cs | 54 ++- .../Managers/AwsDeviceModelImageManager.cs | 167 ++++--- .../Services/AWS/AWSDevicePropertyService.cs | 26 +- .../Services/AWS/AWSDeviceService.cs | 100 ++-- .../Services/AWS/AWSEdgeDevicesService.cs | 60 ++- .../Services/AWS/AwsConfigService.cs | 172 ++++--- .../Services/AwsExternalDeviceService.cs | 443 ++++++++++++------ .../Jobs/AWS/SyncThingTypesJobTests.cs | 2 + .../AwsDeviceModelImageManagerTest.cs | 51 +- .../Services/AWSDevicePropertyServiceTests.cs | 11 +- .../Services/AWSDeviceServiceTests.cs | 45 +- .../Services/AwsExternalDeviceServiceTests.cs | 42 +- 13 files changed, 764 insertions(+), 456 deletions(-) diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs index b432151f8..58f7a9bb3 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDeploymentsJob.cs @@ -89,31 +89,44 @@ private async Task> GetAllGreenGrassDeployments() HistoryFilter = DeploymentHistoryFilter.LATEST_ONLY }; - var response = await this.amazonGreenGrass.ListDeploymentsAsync(request); - - foreach (var deployment in response.Deployments) + try { - var awsThingGroupRegex = new Regex(@"/([^/]+)$"); - var matches = awsThingGroupRegex.Match(deployment.TargetArn); + var response = await this.amazonGreenGrass.ListDeploymentsAsync(request); - if (matches.Success && matches.Groups.Count > 1) + foreach (var deployment in response.Deployments) { - var thinggroupName = matches.Groups[1].Value; - var s = await this.amazonIoTClient.DescribeThingGroupAsync(new Amazon.IoT.Model.DescribeThingGroupRequest { ThingGroupName = thinggroupName }); - if (s.QueryString != null) + var awsThingGroupRegex = new Regex(@"/([^/]+)$"); + var matches = awsThingGroupRegex.Match(deployment.TargetArn); + + if (matches.Success && matches.Groups.Count > 1) { - var iotEdgeModel = new IoTEdgeModel + var thinggroupName = matches.Groups[1].Value; + try + { + var s = await this.amazonIoTClient.DescribeThingGroupAsync(new Amazon.IoT.Model.DescribeThingGroupRequest { ThingGroupName = thinggroupName }); + if (s.QueryString != null) + { + var iotEdgeModel = new IoTEdgeModel + { + ModelId = deployment.DeploymentId, //Instead of giving a random Id here, we can give the deploymentID + Name = deployment.DeploymentName, + ExternalIdentifier = deployment.DeploymentId + }; + deployments.Add(iotEdgeModel); + } + } + catch (AmazonIoTException e) { - ModelId = deployment.DeploymentId, //Instead of giving a random Id here, we can give the deploymentID - Name = deployment.DeploymentName, - ExternalIdentifier = deployment.DeploymentId - }; - deployments.Add(iotEdgeModel); + throw new Domain.Exceptions.InternalServerErrorException("Unable to Describe The thing group due to an error in the Amazon IoT API.", e); + } } } + nextToken = response.NextToken; + } + catch (AmazonGreengrassV2Exception e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to List The deployments due to an error in the Amazon IoT API.", e); } - - nextToken = response.NextToken; } while (!string.IsNullOrEmpty(nextToken)); diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs index 984a167f6..192f04f77 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs @@ -94,13 +94,25 @@ private async Task> Remove5mnDeprecatedThingType foreach (var thingType in thingTypes) { - var diffInMinutes = (DateTime.Now.Subtract(thingType.ThingTypeMetadata.DeprecationDate)).TotalMinutes; + var diffInMinutes = DateTime.Now.Subtract(thingType.ThingTypeMetadata.DeprecationDate).TotalMinutes; if (thingType.ThingTypeMetadata.Deprecated && diffInMinutes > 5) { - _ = await this.amazonIoTClient.DeleteThingTypeAsync(new DeleteThingTypeRequest + try { - ThingTypeName = thingType.ThingTypeName - }); + _ = await this.amazonIoTClient.DeleteThingTypeAsync(new DeleteThingTypeRequest + { + ThingTypeName = thingType.ThingTypeName + }); + + _ = await this.amazonIoTClient.DeleteDynamicThingGroupAsync(new DeleteDynamicThingGroupRequest + { + ThingGroupName = thingType.ThingTypeName + }); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Delete The thing type due to an error in the Amazon IoT API.", e); + } } else { @@ -123,19 +135,33 @@ private async Task> GetAllThingTypes() NextToken = nextToken }; - var response = await amazonIoTClient.ListThingTypesAsync(request); - - foreach (var thingType in response.ThingTypes) + try { - var requestDescribeThingType = new DescribeThingTypeRequest - { - ThingTypeName = thingType.ThingTypeName, - }; + var response = await amazonIoTClient.ListThingTypesAsync(request); - thingTypes.Add(await this.amazonIoTClient.DescribeThingTypeAsync(requestDescribeThingType)); + foreach (var thingType in response.ThingTypes) + { + var requestDescribeThingType = new DescribeThingTypeRequest + { + ThingTypeName = thingType.ThingTypeName, + }; + + try + { + thingTypes.Add(await this.amazonIoTClient.DescribeThingTypeAsync(requestDescribeThingType)); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Describe The thing type due to an error in the Amazon IoT API.", e); + } + } + + nextToken = response.NextToken; + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to list Thing types due to an error in the Amazon IoT API.", e); } - - nextToken = response.NextToken; } while (!string.IsNullOrEmpty(nextToken)); diff --git a/src/IoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs b/src/IoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs index 0102d0606..358bb8cb1 100644 --- a/src/IoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs +++ b/src/IoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs @@ -8,7 +8,6 @@ namespace IoTHub.Portal.Infrastructure.Managers using System.Threading.Tasks; using Amazon.S3; using Amazon.S3.Model; - using Azure; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Exceptions; @@ -38,39 +37,48 @@ public AwsDeviceModelImageManager( public async Task ChangeDeviceModelImageAsync(string deviceModelId, Stream stream) { - this.logger.LogInformation($"Uploading Image to AWS S3 storage"); + this.logger.LogInformation($"Uploading Image to AWS S3 storage"); - //Portal must be able to upload images to Amazon S3 - var putObjectRequest = new PutObjectRequest - { - BucketName = this.configHandler.AWSBucketName, - Key = deviceModelId, - InputStream = stream, - ContentType = "image/*", - Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } - }; - var putObjectResponse = await this.s3Client.PutObjectAsync(putObjectRequest); - - if (putObjectResponse.HttpStatusCode == System.Net.HttpStatusCode.OK) + try { - //Images on S3 are publicly accessible and read-only - var putAclRequest = new PutACLRequest + //Portal must be able to upload images to Amazon S3 + var putObjectRequest = new PutObjectRequest { BucketName = this.configHandler.AWSBucketName, Key = deviceModelId, - CannedACL = S3CannedACL.PublicRead // Set the object's ACL to public read + InputStream = stream, + ContentType = "image/*", + Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } }; - var putACLResponse = await this.s3Client.PutACLAsync(putAclRequest); - return putACLResponse.HttpStatusCode == System.Net.HttpStatusCode.OK - ? ComputeImageUri(deviceModelId).ToString() - : throw new InternalServerErrorException("Error by setting the image access to public and read-only"); + _ = await this.s3Client.PutObjectAsync(putObjectRequest); + + try + { + //Images on S3 are publicly accessible and read-only + var putAclRequest = new PutACLRequest + { + BucketName = this.configHandler.AWSBucketName, + Key = deviceModelId, + CannedACL = S3CannedACL.PublicRead // Set the object's ACL to public read + }; + + _ = await this.s3Client.PutACLAsync(putAclRequest); + + return ComputeImageUri(deviceModelId).ToString(); + } + catch (AmazonS3Exception e) + { + throw new InternalServerErrorException("Unable to set the image access to public and read-only due to an error in Amazon S3 API.", e); + } + } - else + catch (AmazonS3Exception e) { - throw new InternalServerErrorException("Error by uploading the image in S3 Storage"); + throw new InternalServerErrorException(" Unable to upload the image in S3 Bucket due to an error in Amazon S3 API.", e); } + } public Uri ComputeImageUri(string deviceModelId) @@ -84,61 +92,68 @@ public async Task DeleteDeviceModelImageAsync(string deviceModelId) this.logger.LogInformation($"Deleting image from AWS S3 storage"); - var deleteImageObject = new DeleteObjectRequest - { - BucketName = this.configHandler.AWSBucketName, - Key = deviceModelId - }; try { + var deleteImageObject = new DeleteObjectRequest + { + BucketName = this.configHandler.AWSBucketName, + Key = deviceModelId + }; + _ = await this.s3Client.DeleteObjectAsync(deleteImageObject); } - catch (RequestFailedException e) + catch (AmazonS3Exception e) { - throw new InternalServerErrorException("Unable to delete the image from S3 storage.", e); + throw new InternalServerErrorException("Unable to delete the image from S3 storage due to an error in Amazon S3 API.", e); } } public async Task SetDefaultImageToModel(string deviceModelId) { + this.logger.LogInformation($"Uploading Default Image to AWS S3 storage"); + var currentAssembly = Assembly.GetExecutingAssembly(); var defaultImageStream = currentAssembly .GetManifestResourceStream($"{currentAssembly.GetName().Name}.Resources.{this.imageOptions.Value.DefaultImageName}"); - - //Portal must be able to upload images to Amazon S3 - var putObjectRequest = new PutObjectRequest - { - BucketName = this.configHandler.AWSBucketName, - Key = deviceModelId, - InputStream = defaultImageStream, - ContentType = "image/*", // image content type - Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } - - }; - - var putObjectResponse = await this.s3Client.PutObjectAsync(putObjectRequest); - - if (putObjectResponse.HttpStatusCode == System.Net.HttpStatusCode.OK) + try { - //Images on S3 are publicly accessible and read-only - var putAclRequest = new PutACLRequest + //Portal must be able to upload images to Amazon S3 + var putObjectRequest = new PutObjectRequest { BucketName = this.configHandler.AWSBucketName, Key = deviceModelId, - CannedACL = S3CannedACL.PublicRead // Set the object's ACL to public read + InputStream = defaultImageStream, + ContentType = "image/*", // image content type + Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } + }; - var putACLResponse = await this.s3Client.PutACLAsync(putAclRequest); - return putACLResponse.HttpStatusCode == System.Net.HttpStatusCode.OK - ? ComputeImageUri(deviceModelId).ToString() - : throw new InternalServerErrorException("Error by setting the image access to public and read-only"); + _ = await this.s3Client.PutObjectAsync(putObjectRequest); + + try + { + //Images on S3 are publicly accessible and read-only + var putAclRequest = new PutACLRequest + { + BucketName = this.configHandler.AWSBucketName, + Key = deviceModelId, + CannedACL = S3CannedACL.PublicRead // Set the object's ACL to public read + }; + _ = await this.s3Client.PutACLAsync(putAclRequest); + + return ComputeImageUri(deviceModelId).ToString(); + } + catch (AmazonS3Exception e) + { + throw new InternalServerErrorException("Unable to set the image access to public and read-only due to an error in Amazon S3 API.", e); + } } - else + catch (AmazonS3Exception e) { - throw new InternalServerErrorException("Error by uploading the image in S3 Storage"); + throw new InternalServerErrorException("Unable to upload the image in S3 Bucket due to an error in Amazon S3 API.", e); } } @@ -152,37 +167,41 @@ public async Task InitializeDefaultImageBlob() var defaultImageStream = currentAssembly .GetManifestResourceStream($"{currentAssembly.GetName().Name}.Resources.{this.imageOptions.Value.DefaultImageName}"); - var putObjectRequest = new PutObjectRequest - { - BucketName = this.configHandler.AWSBucketName, - Key = this.imageOptions.Value.DefaultImageName, - InputStream = defaultImageStream, - ContentType = "image/*", // image content type - Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } - - }; - - var putObjectResponse = await this.s3Client.PutObjectAsync(putObjectRequest); - if (putObjectResponse.HttpStatusCode == System.Net.HttpStatusCode.OK) + try { - //Images on S3 are publicly accessible and read-only - var putAclRequest = new PutACLRequest + var putObjectRequest = new PutObjectRequest { BucketName = this.configHandler.AWSBucketName, Key = this.imageOptions.Value.DefaultImageName, - CannedACL = S3CannedACL.PublicRead // Set the object's ACL to public read + InputStream = defaultImageStream, + ContentType = "image/*", // image content type + Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } + }; - var putACLResponse = await this.s3Client.PutACLAsync(putAclRequest); - if (putACLResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + _ = await this.s3Client.PutObjectAsync(putObjectRequest); + + try + { + //Images on S3 are publicly accessible and read-only + var putAclRequest = new PutACLRequest + { + BucketName = this.configHandler.AWSBucketName, + Key = this.imageOptions.Value.DefaultImageName, + CannedACL = S3CannedACL.PublicRead // Set the object's ACL to public read + }; + + _ = await this.s3Client.PutACLAsync(putAclRequest); + } + catch (AmazonS3Exception e) { - throw new InternalServerErrorException("Error by setting the image access to public and read-only"); + throw new InternalServerErrorException("Unable to set the image access to public and read-only due to an error in Amazon S3 API.", e); } } - else + catch (AmazonS3Exception e) { - throw new InternalServerErrorException("Error by uploading the image in S3 Storage"); + throw new InternalServerErrorException("Unable to upload the image in S3 Bucket due to an error in Amazon S3 API.", e); } } diff --git a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs index 9ca7611ba..9a3c94e2a 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSDevicePropertyService.cs @@ -46,13 +46,17 @@ public async Task> GetProperties(string deviceI throw new ResourceNotFoundException($"Unable to find the device {deviceId} in DB"); } - var shadowResponse = await this.amazonIotDataClient.GetThingShadowAsync(new GetThingShadowRequest + GetThingShadowResponse shadowResponse; + try { - ThingName = deviceDb.Name - }); - if (shadowResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + shadowResponse = await this.amazonIotDataClient.GetThingShadowAsync(new GetThingShadowRequest + { + ThingName = deviceDb.Name + }); + } + catch (AmazonIotDataException e) { - throw new InternalServerErrorException($"Unable to get the thing shadow with device name : {deviceDb.Name} due to an error in the Amazon IoT API : {shadowResponse.HttpStatusCode}"); + throw new InternalServerErrorException($"Unable to get the thing shadow with device name : {deviceDb.Name} due to an error in the Amazon IoT API", e); } IEnumerable items; @@ -120,7 +124,7 @@ public async Task SetProperties(string deviceId, IEnumerable(); @@ -148,11 +152,13 @@ public async Task SetProperties(string deviceId, IEnumerable CreateDevice(DeviceDetails device) { //Create Thing - var thingResponse = await this.amazonIoTClient.CreateThingAsync(this.mapper.Map(device)); - if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + try { - throw new InternalServerErrorException($"Unable to create the thing with device name : {device.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); - } - device.DeviceID = thingResponse.ThingId; + var thingResponse = await this.amazonIoTClient.CreateThingAsync(this.mapper.Map(device)); + device.DeviceID = thingResponse.ThingId; + + try + { + //Create Thing Shadow + var shadowResponse = await this.amazonIotDataClient.UpdateThingShadowAsync(this.mapper.Map(device)); + + //Create Thing in DB + return await CreateDeviceInDatabase(device); + } + catch (AmazonIotDataException e) + { + throw new InternalServerErrorException($"Unable to create/update the thing shadow with device name : {device.DeviceName} due to an error in the Amazon IoT API.", e); + } - //Create Thing Shadow - var shadowResponse = await this.amazonIotDataClient.UpdateThingShadowAsync(this.mapper.Map(device)); - if (shadowResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + } + catch (AmazonIoTException e) { - throw new InternalServerErrorException($"Unable to create/update the thing shadow with device name : {device.DeviceName} due to an error in the Amazon IoT API : {shadowResponse.HttpStatusCode}"); + throw new InternalServerErrorException($"Unable to create the thing with device name : {device.DeviceName} due to an error in the Amazon IoT API.", e); + } - //Create Thing in DB - return await CreateDeviceInDatabase(device); } public override async Task UpdateDevice(DeviceDetails device) { - //Update Thing - var thingResponse = await this.amazonIoTClient.UpdateThingAsync(this.mapper.Map(device)); - if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + try + { + //Update Thing + var thingResponse = await this.amazonIoTClient.UpdateThingAsync(this.mapper.Map(device)); + + //Update Thing in DB + return await UpdateDeviceInDatabase(device); + } + catch (AmazonIoTException e) { - throw new InternalServerErrorException($"Unable to update the thing with device name : {device.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); + throw new InternalServerErrorException($"Unable to update the thing with device name : {device.DeviceName} due to an error in the Amazon IoT API.", e); } - //Update Thing in DB - return await UpdateDeviceInDatabase(device); } public override async Task DeleteDevice(string deviceId) @@ -95,38 +108,47 @@ public override async Task DeleteDevice(string deviceId) try { - //Retrieve all thing principals and detach it before deleting the thing - var principals = await this.amazonIoTClient.ListThingPrincipalsAsync(new ListThingPrincipalsRequest + try { - NextToken = string.Empty, - ThingName = device.Name - }); - - if (principals.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException($"Unable to retreive Thing {device.Name} principals due to an error in the Amazon IoT API : {principals.HttpStatusCode}"); - } - - foreach (var principal in principals.Principals) - { - var detachPrincipal = await this.amazonIoTClient.DetachThingPrincipalAsync(new DetachThingPrincipalRequest + //Retrieve all thing principals and detach it before deleting the thing + var principals = await this.amazonIoTClient.ListThingPrincipalsAsync(new ListThingPrincipalsRequest { - Principal = principal, + NextToken = string.Empty, ThingName = device.Name }); - if (detachPrincipal.HttpStatusCode != System.Net.HttpStatusCode.OK) + try { - throw new InternalServerErrorException($"Unable to detach Thing {device.Name} principal due to an error in the Amazon IoT API : {detachPrincipal.HttpStatusCode}"); + foreach (var principal in principals.Principals) + { + _ = await this.amazonIoTClient.DetachThingPrincipalAsync(new DetachThingPrincipalRequest + { + Principal = principal, + ThingName = device.Name + }); + } + } + catch (AmazonIoTException e) + { + this.logger.LogWarning("Can not detach Thing principal because it doesn't exist in AWS IoT", e); } - } - //Delete the thing type after detaching the principal - var deleteResponse = await this.amazonIoTClient.DeleteThingAsync(this.mapper.Map(device)); - if (deleteResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + try + { + //Delete the thing type after detaching the principal + _ = await this.amazonIoTClient.DeleteThingAsync(this.mapper.Map(device)); + } + catch (AmazonIoTException e) + { + this.logger.LogWarning("Can not delete the thing because it doesn't exist in AWS IoT", e); + } + + } + catch (AmazonIoTException e) { - throw new InternalServerErrorException($"Unable to delete the thing with device name : {device.Name} due to an error in the Amazon IoT API : {deleteResponse.HttpStatusCode}"); + this.logger.LogWarning("Can not retreive Thing because it doesn't exist in AWS IoT", e); } + } catch (AmazonIoTException e) { diff --git a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs index f352186ea..ebcc7556d 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs @@ -87,18 +87,24 @@ public async Task CreateEdgeDevice(IoTEdgeDevice edgeDevice) //Create Thing var createThingRequest = this.mapper.Map(edgeDevice); createThingRequest.ThingTypeName = model.Name; - var thingResponse = await this.amazonIoTClient.CreateThingAsync(createThingRequest); - if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + + try { - throw new InternalServerErrorException($"Unable to create the thing with device name : {edgeDevice.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); - } - edgeDevice.DeviceId = thingResponse.ThingId; + var thingResponse = await this.amazonIoTClient.CreateThingAsync(createThingRequest); + edgeDevice.DeviceId = thingResponse.ThingId; - //Create EdgeDevice in DB - var result = await base.CreateEdgeDeviceInDatabase(edgeDevice); - await this.unitOfWork.SaveAsync(); + //Create EdgeDevice in DB + var result = await base.CreateEdgeDeviceInDatabase(edgeDevice); + await this.unitOfWork.SaveAsync(); + + return result; + + } + catch (AmazonIoTException e) + { + throw new InternalServerErrorException($"Unable to create the thing with device name : {edgeDevice.DeviceName} due to an error in the Amazon IoT API.", e); + } - return result; } @@ -111,18 +117,22 @@ public async Task UpdateEdgeDevice(IoTEdgeDevice edgeDevice) { ArgumentNullException.ThrowIfNull(edgeDevice, nameof(edgeDevice)); - //Update Thing - var thingResponse = await this.amazonIoTClient.UpdateThingAsync(this.mapper.Map(edgeDevice)); - if (thingResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + try { - throw new InternalServerErrorException($"Unable to update the thing with device name : {edgeDevice.DeviceName} due to an error in the Amazon IoT API : {thingResponse.HttpStatusCode}"); - } + //Update Thing + _ = await this.amazonIoTClient.UpdateThingAsync(this.mapper.Map(edgeDevice)); - //Update EdgeDevice in DB - var result = await UpdateEdgeDeviceInDatabase(edgeDevice); - await this.unitOfWork.SaveAsync(); + //Update EdgeDevice in DB + var result = await UpdateEdgeDeviceInDatabase(edgeDevice); + await this.unitOfWork.SaveAsync(); + + return result; + } + catch (AmazonIoTException e) + { + throw new InternalServerErrorException($"Unable to update the thing with device name : {edgeDevice.DeviceName} due to an error in the Amazon IoT API.", e); + } - return result; } @@ -208,14 +218,20 @@ public async Task GetEdgeDeviceEnrollementScript(string deviceId, string private async Task GetEdgeDeviceNbDevices(IoTEdgeDevice device) { - var listClientDevices = await this.amazonGreengrass.ListClientDevicesAssociatedWithCoreDeviceAsync( + try + { + var listClientDevices = await this.amazonGreengrass.ListClientDevicesAssociatedWithCoreDeviceAsync( new Amazon.GreengrassV2.Model.ListClientDevicesAssociatedWithCoreDeviceRequest { CoreDeviceThingName = device.DeviceName }); - return listClientDevices.HttpStatusCode != System.Net.HttpStatusCode.OK - ? throw new InternalServerErrorException($"Can not list Client Devices Associated to {device.DeviceName} Core Device due to an error in the Amazon IoT API.") - : listClientDevices.AssociatedClientDevices.Count; + + return listClientDevices.AssociatedClientDevices.Count; + } + catch (AmazonGreengrassV2Exception e) + { + throw new InternalServerErrorException($"Can not list Client Devices Associated to {device.DeviceName} Core Device due to an error in the Amazon IoT API.", e); + } } } diff --git a/src/IoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs b/src/IoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs index 043ec8c48..12cd34363 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs @@ -4,7 +4,6 @@ namespace IoTHub.Portal.Infrastructure.Services.AWS { using System.Collections.Generic; - using System.Net; using System.Text; using System.Threading.Tasks; using Amazon.GreengrassV2; @@ -56,14 +55,18 @@ public async Task RollOutEdgeModelConfiguration(IoTEdgeModel edgeModel) TargetArn = await GetThingGroupArn(edgeModel!) }; - var createDeploymentResponse = await this.greengrass.CreateDeploymentAsync(createDeploymentRequest); + try + { + var createDeploymentResponse = await this.greengrass.CreateDeploymentAsync(createDeploymentRequest); + + return createDeploymentResponse.DeploymentId; - if (createDeploymentResponse.HttpStatusCode != HttpStatusCode.Created) + } + catch (AmazonGreengrassV2Exception e) { - throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API."); + throw new InternalServerErrorException("The deployment creation failed due to an error in the Amazon IoT API.", e); } - return createDeploymentResponse.DeploymentId; } private async Task GetThingGroupArn(IoTEdgeModel edgeModel) @@ -83,13 +86,20 @@ private async Task GetThingGroupArn(IoTEdgeModel edgeModel) } catch (Amazon.IoT.Model.ResourceNotFoundException) { - var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest + try { - ThingGroupName = edgeModel!.Name, - QueryString = $"thingTypeName: {edgeModel!.Name}" - }); + var createThingGroupResponse = await this.iotClient.CreateDynamicThingGroupAsync(new CreateDynamicThingGroupRequest + { + ThingGroupName = edgeModel!.Name, + QueryString = $"thingTypeName: {edgeModel!.Name}" + }); - return createThingGroupResponse.ThingGroupArn; + return createThingGroupResponse.ThingGroupArn; + } + catch (AmazonIoTException e) + { + throw new InternalServerErrorException("The creation of the dynamic thing group failed due to an error in the Amazon IoT API.", e); + } } } @@ -106,10 +116,12 @@ private async Task CreateThingTypeIfNotExists(string thingTypeName) } catch (Amazon.IoT.Model.ResourceNotFoundException) { - _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest + try { - ThingTypeName = thingTypeName, - Tags = new List + _ = await this.iotClient.CreateThingTypeAsync(new CreateThingTypeRequest + { + ThingTypeName = thingTypeName, + Tags = new List { new Tag { @@ -117,7 +129,12 @@ private async Task CreateThingTypeIfNotExists(string thingTypeName) Value = "True" } } - }); + }); + } + catch (AmazonIoTException e) + { + throw new InternalServerErrorException("Unable to create the thing type due to an error in the Amazon IoT API.", e); + } } } @@ -130,7 +147,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}"; // + $"arn:aws:greengrass:{this.config.AWSRegion}:{this.config.AWSAccountId}:components:{component.ModuleName}:versions:{component.Version}"; // Private greengrass component _ = await this.greengrass.DescribeComponentAsync(new DescribeComponentRequest { @@ -145,13 +162,17 @@ private async Task> CreateG { InlineRecipe = new MemoryStream(Encoding.UTF8.GetBytes(component.ContainerCreateOptions)) }; - var response = await greengrass.CreateComponentVersionAsync(componentVersion); - if (response.HttpStatusCode != HttpStatusCode.Created) + + try { - throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API."); + _ = await greengrass.CreateComponentVersionAsync(componentVersion); + components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); + } + catch (AmazonGreengrassV2Exception e) + { + throw new InternalServerErrorException("The component creation failed due to an error in the Amazon IoT API.", e); } - components.Add(component.ModuleName, new ComponentDeploymentSpecification { ComponentVersion = component.Version }); } } @@ -199,40 +220,44 @@ public async Task DeleteConfiguration(string modelId) foreach (var module in modules.Where(c => string.IsNullOrEmpty(c.Id))) { - var deletedComponentResponse = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest + try { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" - }); - - if (deletedComponentResponse.HttpStatusCode != HttpStatusCode.NoContent) + _ = await this.greengrass.DeleteComponentAsync(new DeleteComponentRequest + { + Arn = $"arn:aws:greengrass:{this.config.AWSRegion}:{this.config.AWSAccountId}:components:{module.ModuleName}:versions:{module.Version}" + }); + } + catch (AmazonGreengrassV2Exception e) { - throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API."); + throw new InternalServerErrorException("The deletion of the component failed due to an error in the Amazon IoT API.", e); } } - var cancelDeploymentResponse = await this.greengrass.CancelDeploymentAsync(new CancelDeploymentRequest + try { - DeploymentId = modelId - }); - - if (cancelDeploymentResponse.HttpStatusCode != HttpStatusCode.OK) + _ = await this.greengrass.CancelDeploymentAsync(new CancelDeploymentRequest + { + DeploymentId = modelId + }); + } + catch (AmazonGreengrassV2Exception e) { - throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API."); - + throw new InternalServerErrorException("The cancellation of the deployment failed due to an error in the Amazon IoT API.", e); } - else + + try { - var deleteDeploymentResponse = await this.greengrass.DeleteDeploymentAsync(new DeleteDeploymentRequest + _ = 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."); - } + } + catch (AmazonGreengrassV2Exception e) + { + throw new InternalServerErrorException("The deletion of the deployment failed due to an error in the Amazon IoT API.", e); } + } private async Task DeprecateDeploymentThingType(string modelId) @@ -244,36 +269,50 @@ private async Task DeprecateDeploymentThingType(string modelId) DeploymentId = modelId }); - var deploymentThingType = await this.iotClient.DeprecateThingTypeAsync(new DeprecateThingTypeRequest + try { - ThingTypeName = deployment.DeploymentName - }); - + _ = await this.iotClient.DeprecateThingTypeAsync(new DeprecateThingTypeRequest + { + ThingTypeName = deployment.DeploymentName + }); + } + catch (AmazonIoTException e) + { + throw new InternalServerErrorException($"Unable to deprecate the Thing type associated with {deployment.DeploymentName} due to an error in the Amazon IoT API", e); + } } catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) { - throw new InternalServerErrorException("The deployment is not found"); - + throw new InternalServerErrorException("Unable to find the deployment due to an error in the Amazon IoT API."); } } public async Task GetFailedDeploymentsCount() { + var failedDeploymentCount = 0; - var deployments = await this.greengrass.ListDeploymentsAsync(new ListDeploymentsRequest + try { - NextToken = string.Empty - }); + var deployments = await this.greengrass.ListDeploymentsAsync(new ListDeploymentsRequest + { + NextToken = string.Empty + }); - foreach (var deployment in deployments.Deployments) - { - if (deployment.DeploymentStatus.Equals(DeploymentStatus.FAILED)) + foreach (var deployment in deployments.Deployments) { - failedDeploymentCount++; + if (deployment.DeploymentStatus.Equals(DeploymentStatus.FAILED)) + { + failedDeploymentCount++; + } } + + return failedDeploymentCount; + } + catch (AmazonGreengrassV2Exception e) + { + throw new InternalServerErrorException("Unable to get the list of the deployments due to an error in the Amazon IoT API.", e); } - return failedDeploymentCount; } public async Task> GetConfigModuleList(string modelId) @@ -297,7 +336,7 @@ public async Task> GetConfigModuleList(string modelId) { var responseComponent = await this.greengrass.GetComponentAsync(new GetComponentRequest { - Arn = $"arn:aws:greengrass:{config.AWSRegion}:{config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", + Arn = $"arn:aws:greengrass:{this.config.AWSRegion}:{this.config.AWSAccountId}:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", RecipeOutputFormat = RecipeOutputFormat.JSON }); @@ -308,11 +347,11 @@ public async Task> GetConfigModuleList(string modelId) catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) { // If the component is not found, we assume it is a public component - componentId = $"arn:aws:greengrass:{config.AWSRegion}:aws:components:{compoenent.Key}"; + componentId = $"arn:aws:greengrass:{this.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}", + Arn = $"arn:aws:greengrass:{this.config.AWSRegion}:aws:components:{compoenent.Key}:versions:{compoenent.Value.ComponentVersion}", RecipeOutputFormat = RecipeOutputFormat.JSON }); @@ -337,7 +376,7 @@ public async Task> GetConfigModuleList(string modelId) } catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) { - throw new InternalServerErrorException("The deployment is not found"); + throw new InternalServerErrorException("Unable to find the deployment due to an error in the Amazon IoT API. "); } } @@ -360,15 +399,22 @@ public async Task> GetPublicEdgeModules() do { - var response = await this.greengrass.ListComponentsAsync(new ListComponentsRequest + try { - Scope = ComponentVisibilityScope.PUBLIC, - NextToken = nextToken - }); + var response = await this.greengrass.ListComponentsAsync(new ListComponentsRequest + { + Scope = ComponentVisibilityScope.PUBLIC, + NextToken = nextToken + }); - publicComponents.AddRange(response.Components); + publicComponents.AddRange(response.Components); - nextToken = response.NextToken; + nextToken = response.NextToken; + } + catch (AmazonGreengrassV2Exception e) + { + throw new InternalServerErrorException("Unable to list the public components due to an error in the Amazon IoT API.", e); + } } while (!string.IsNullOrEmpty(nextToken)); diff --git a/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs b/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs index 676e39521..cb0949688 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AwsExternalDeviceService.cs @@ -4,7 +4,6 @@ namespace IoTHub.Portal.Infrastructure.Services { using System.Collections.Generic; - using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; using Amazon.GreengrassV2; @@ -12,6 +11,7 @@ namespace IoTHub.Portal.Infrastructure.Services using Amazon.IoT; using Amazon.IoT.Model; using Amazon.IotData; + using Amazon.IotData.Model; using Amazon.SecretsManager; using Amazon.SecretsManager.Model; using AutoMapper; @@ -24,6 +24,7 @@ namespace IoTHub.Portal.Infrastructure.Services using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; using Shared.Models.v10; + using Device = Microsoft.Azure.Devices.Device; using ListTagsForResourceRequest = Amazon.IoT.Model.ListTagsForResourceRequest; using ResourceAlreadyExistsException = Amazon.IoT.Model.ResourceAlreadyExistsException; using ResourceNotFoundException = Amazon.IoT.Model.ResourceNotFoundException; @@ -76,7 +77,7 @@ public async Task CreateDeviceModel(ExternalDeviceModelD }); var response = await this.amazonIoTClient.CreateThingTypeAsync(createThingTypeRequest); - await this.CreateDynamicGroupForThingType(response.ThingTypeName); + await CreateDynamicGroupForThingType(response.ThingTypeName); deviceModel.Id = response.ThingTypeId; @@ -84,7 +85,11 @@ public async Task CreateDeviceModel(ExternalDeviceModelD } catch (ResourceAlreadyExistsException e) { - throw new Domain.Exceptions.ResourceAlreadyExistsException($"Unable to create the device model {deviceModel.Name}: {e.Message}", e); + throw new Domain.Exceptions.ResourceAlreadyExistsException($"Device Model already exists. Unable to create the device model {deviceModel.Name}: {e.Message}", e); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to create the device model {deviceModel.Name}: {e.Message}", e); } } @@ -102,7 +107,10 @@ public async Task DeleteDevice(string deviceId) CoreDeviceThingName = deviceId, }); } - catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) { } + catch (Amazon.GreengrassV2.Model.ResourceNotFoundException) + { + this.logger.LogWarning("Unable to Delete Core Device because it doesn't exist"); + } try { @@ -111,7 +119,11 @@ public async Task DeleteDevice(string deviceId) ThingName = deviceId }); } - catch (Amazon.IoT.Model.ResourceNotFoundException) { } + catch (ResourceNotFoundException e) + { + this.logger.LogWarning("Unable to delete the thing because it doesn't exist", e); + } + } public async Task DeleteDeviceModel(ExternalDeviceModelDto deviceModel) @@ -126,52 +138,94 @@ public async Task DeleteDeviceModel(ExternalDeviceModelDto deviceModel) _ = await this.amazonIoTClient.DeprecateThingTypeAsync(deprecated); - _ = await this.amazonIoTClient.DeleteDynamicThingGroupAsync(new DeleteDynamicThingGroupRequest + try { - ThingGroupName = deviceModel.Name - }); + _ = await this.amazonIoTClient.DeleteDynamicThingGroupAsync(new DeleteDynamicThingGroupRequest + { + ThingGroupName = deviceModel.Name + }); + } + catch (ResourceNotFoundException e) + { + throw new Domain.Exceptions.ResourceNotFoundException($"Thing Group not found. Unable to delete the device model {deviceModel.Name}: {e.Message}", e); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to delete the device model {deviceModel.Name}: {e.Message}", e); + } + } catch (ResourceNotFoundException e) { - throw new Domain.Exceptions.ResourceNotFoundException($"Unable to delete the device model {deviceModel.Name}: {e.Message}", e); + throw new Domain.Exceptions.ResourceNotFoundException($"Thing type not Found. Unable to deprecate the device model {deviceModel.Name}: {e.Message}", e); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to delete the device model {deviceModel.Name}: {e.Message}", e); } } public async Task IsEdgeDeviceModel(ExternalDeviceModelDto deviceModel) { - var thingType = await this.amazonIoTClient.DescribeThingTypeAsync(new DescribeThingTypeRequest() - { - ThingTypeName = deviceModel.Name - }); - var response = await this.amazonIoTClient.ListTagsForResourceAsync(new ListTagsForResourceRequest - { - ResourceArn = thingType.ThingTypeArn - }); - - do + try { - if (response == null || !response.Tags.Any()) + var thingType = await this.amazonIoTClient.DescribeThingTypeAsync(new DescribeThingTypeRequest() { - return null; - } - - var iotEdgeTag = response.Tags.Where(c => c.Key.Equals("iotEdge", StringComparison.OrdinalIgnoreCase)); + ThingTypeName = deviceModel.Name + }); - if (!iotEdgeTag.Any()) + try { - response = await this.amazonIoTClient.ListTagsForResourceAsync(new ListTagsForResourceRequest + var response = await this.amazonIoTClient.ListTagsForResourceAsync(new ListTagsForResourceRequest { - ResourceArn = thingType.ThingTypeArn, - NextToken = response.NextToken + ResourceArn = thingType.ThingTypeArn }); - continue; + do + { + if (response == null || !response.Tags.Any()) + { + return null; + } + + var iotEdgeTag = response.Tags.Where(c => c.Key.Equals("iotEdge", StringComparison.OrdinalIgnoreCase)); + + if (!iotEdgeTag.Any()) + { + try + { + response = await this.amazonIoTClient.ListTagsForResourceAsync(new ListTagsForResourceRequest + { + ResourceArn = thingType.ThingTypeArn, + NextToken = response.NextToken + }); + + continue; + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to list tags thing type {deviceModel.Name}: {e.Message}", e); + + } + } + + return bool.TryParse(iotEdgeTag.Single().Value, out var result) ? result : null; + + } while (true); } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to list tags thing type {deviceModel.Name}: {e.Message}", e); + + } + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to describe the the thing type {deviceModel.Name}: {e.Message}", e); - return bool.TryParse(iotEdgeTag.Single().Value, out var result) ? result : null; + } - } while (true); } public Task ExecuteC2DMethod(string deviceId, CloudToDeviceMethod method) @@ -202,23 +256,30 @@ public async Task> GetAllThing() Marker = marker }; - var response = await this.amazonIoTClient.ListThingsAsync(request); - - foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName })) + try { - try - { - things.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); - } - catch (AmazonIoTException e) + var response = await this.amazonIoTClient.ListThingsAsync(request); + + foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName })) { - this.logger.LogWarning($"Cannot import device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); + try + { + things.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); + } + catch (AmazonIoTException e) + { + this.logger.LogWarning($"Cannot import device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); - continue; + continue; + } } - } - marker = response.NextMarker; + marker = response.NextMarker; + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to list thing types : {e.Message}", e); + } } while (!string.IsNullOrEmpty(marker)); @@ -247,12 +308,19 @@ public Task GetConnectedDevicesCount() public async Task GetConnectedEdgeDevicesCount() { - var coreDevices = await this.greengrass.ListCoreDevicesAsync(new ListCoreDevicesRequest + try { - NextToken = string.Empty - }); + var coreDevices = await this.greengrass.ListCoreDevicesAsync(new ListCoreDevicesRequest + { + NextToken = string.Empty + }); - return coreDevices.CoreDevices.Where(c => c.Status == CoreDeviceStatus.HEALTHY).Count(); + return coreDevices.CoreDevices.Where(c => c.Status == CoreDeviceStatus.HEALTHY).Count(); + } + catch (AmazonGreengrassV2Exception e) + { + throw new Domain.Exceptions.InternalServerErrorException($"Unable to List Core Devices due to an error in the Amazon IoT API.", e); + } } public Task GetDevice(string deviceId) @@ -306,18 +374,15 @@ private async Task> Count() { try { - var thingShadow = await this.amazonIotData.GetThingShadowAsync(new Amazon.IotData.Model.GetThingShadowRequest + _ = await this.amazonIotData.GetThingShadowAsync(new GetThingShadowRequest { ThingName = device.ThingName, }); - if (thingShadow.HttpStatusCode != HttpStatusCode.OK) - { - if (thingShadow.HttpStatusCode.Equals(HttpStatusCode.NotFound)) - this.logger.LogInformation($"Cannot import device '{device.ThingName}' since it doesn't have related classic thing shadow"); - else - this.logger.LogWarning($"Cannot import device '{device.ThingName}' due to an error retrieving thing shadow in the Amazon IoT API : {thingShadow.HttpStatusCode}"); - continue; - } + } + catch (ResourceNotFoundException e) + { + this.logger.LogInformation($"Cannot import device '{device.ThingName}' since it doesn't have related classic thing shadow", e); + continue; } catch (AmazonIotDataException e) { @@ -459,63 +524,108 @@ private async Task CreateDynamicGroupForThingType(string thingTypeName) private async Task> GenerateCertificate(string deviceName) { - var response = await this.amazonIoTClient.CreateKeysAndCertificateAsync(true); - _ = await this.amazonIoTClient.AttachThingPrincipalAsync(deviceName, response.CertificateArn); + try + { + var response = await this.amazonIoTClient.CreateKeysAndCertificateAsync(true); - _ = await CreatePrivateKeySecret(deviceName, response.KeyPair.PrivateKey); - _ = await CreatePublicKeySecret(deviceName, response.KeyPair.PublicKey); - _ = await CreateCertificateSecret(deviceName, response.CertificatePem); - _ = await AttachCertificateToThing(deviceName, response.CertificateArn); + try + { + _ = await this.amazonIoTClient.AttachThingPrincipalAsync(deviceName, response.CertificateArn); - return new Tuple(new DeviceCredentials - { - AuthenticationMode = AuthenticationMode.Certificate, - CertificateCredentials = new CertificateCredentials + _ = await CreatePrivateKeySecret(deviceName, response.KeyPair.PrivateKey); + _ = await CreatePublicKeySecret(deviceName, response.KeyPair.PublicKey); + _ = await CreateCertificateSecret(deviceName, response.CertificatePem); + _ = await AttachCertificateToThing(deviceName, response.CertificateArn); + + return new Tuple(new DeviceCredentials + { + AuthenticationMode = AuthenticationMode.Certificate, + CertificateCredentials = new CertificateCredentials + { + CertificatePem = response.CertificatePem, + PrivateKey = response.KeyPair.PrivateKey, + PublicKey = response.KeyPair.PublicKey, + } + }, response.CertificateArn); + + } + catch (AmazonIoTException e) { - CertificatePem = response.CertificatePem, - PrivateKey = response.KeyPair.PrivateKey, - PublicKey = response.KeyPair.PublicKey, + throw new Domain.Exceptions.InternalServerErrorException("Unable to Attach Thing Principal due to an error in the Amazon IoT API.", e); } - }, response.CertificateArn); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Create Keys and Certificate due to an error in the Amazon IoT API.", e); + } } private async Task AttachCertificateToThing(string deviceName, string certificateArn) { - return await this.amazonIoTClient.AttachThingPrincipalAsync(deviceName, certificateArn); + try + { + return await this.amazonIoTClient.AttachThingPrincipalAsync(deviceName, certificateArn); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Attach Thing Principal due to an error in the Amazon IoT API.", e); + } } private async Task CreatePrivateKeySecret(string deviceName, string privateKey) { - var request = new CreateSecretRequest + try { - Name = deviceName + PrivateKeyKey, - Description = "Private key for the certificate of device " + deviceName, - SecretString = privateKey - }; - return await this.amazonSecretsManager.CreateSecretAsync(request); + var request = new CreateSecretRequest + { + Name = deviceName + PrivateKeyKey, + Description = "Private key for the certificate of device " + deviceName, + SecretString = privateKey + }; + return await this.amazonSecretsManager.CreateSecretAsync(request); + } + catch (AmazonSecretsManagerException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Create Private Secret due to an error in the Amazon IoT API.", e); + } } private async Task CreatePublicKeySecret(string deviceName, string privateKey) { - var request = new CreateSecretRequest + try { - Name = deviceName + PublicKeyKey, - Description = "Public key for the certificate of device " + deviceName, - SecretString = privateKey - }; - return await this.amazonSecretsManager.CreateSecretAsync(request); + var request = new CreateSecretRequest + { + Name = deviceName + PublicKeyKey, + Description = "Public key for the certificate of device " + deviceName, + SecretString = privateKey + }; + return await this.amazonSecretsManager.CreateSecretAsync(request); + } + catch (AmazonSecretsManagerException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Create Public Secret due to an error in the Amazon IoT API.", e); + } } private async Task CreateCertificateSecret(string deviceName, string certificatePem) { - var request = new CreateSecretRequest + try { - Name = deviceName + CertificateKey, - Description = "Certificate for the certificate of device " + deviceName, - SecretString = certificatePem - }; - return await this.amazonSecretsManager.CreateSecretAsync(request); + var request = new CreateSecretRequest + { + Name = deviceName + CertificateKey, + Description = "Certificate for the certificate of device " + deviceName, + SecretString = certificatePem + }; + return await this.amazonSecretsManager.CreateSecretAsync(request); + } + catch (AmazonSecretsManagerException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Create Certificate due to an error in the Amazon IoT API.", e); + + } } @@ -546,26 +656,33 @@ private async Task GetSecret(string secretId) public async Task CreateEnrollementScript(string template, Domain.Entities.EdgeDevice device) { - var iotDataEndpointResponse = await this.amazonIoTClient.DescribeEndpointAsync(new DescribeEndpointRequest + try { - EndpointType = "iot:Data-ATS" - }); + var iotDataEndpointResponse = await this.amazonIoTClient.DescribeEndpointAsync(new DescribeEndpointRequest + { + EndpointType = "iot:Data-ATS" + }); - var credentialProviderEndpointResponse = await this.amazonIoTClient.DescribeEndpointAsync(new DescribeEndpointRequest - { - EndpointType = "iot:CredentialProvider" - }); + var credentialProviderEndpointResponse = await this.amazonIoTClient.DescribeEndpointAsync(new DescribeEndpointRequest + { + EndpointType = "iot:CredentialProvider" + }); - var credentials = await this.GetDeviceCredentials(device.Name); + var credentials = await this.GetDeviceCredentials(device.Name); - return template.Replace("%DATA_ENDPOINT%", iotDataEndpointResponse!.EndpointAddress, StringComparison.OrdinalIgnoreCase) - .Replace("%CREDENTIALS_ENDPOINT%", credentialProviderEndpointResponse.EndpointAddress, StringComparison.OrdinalIgnoreCase) - .Replace("%CERTIFICATE%", credentials.CertificateCredentials.CertificatePem, StringComparison.OrdinalIgnoreCase) - .Replace("%PRIVATE_KEY%", credentials.CertificateCredentials.PrivateKey, StringComparison.OrdinalIgnoreCase) - .Replace("%REGION%", this.configHandler.AWSRegion, StringComparison.OrdinalIgnoreCase) - .Replace("%GREENGRASSCORETOKENEXCHANGEROLEALIAS%", this.configHandler.AWSGreengrassCoreTokenExchangeRoleAliasName, StringComparison.OrdinalIgnoreCase) - .Replace("%THING_NAME%", device.Name, StringComparison.OrdinalIgnoreCase) - .ReplaceLineEndings(); + return template.Replace("%DATA_ENDPOINT%", iotDataEndpointResponse!.EndpointAddress, StringComparison.OrdinalIgnoreCase) + .Replace("%CREDENTIALS_ENDPOINT%", credentialProviderEndpointResponse.EndpointAddress, StringComparison.OrdinalIgnoreCase) + .Replace("%CERTIFICATE%", credentials.CertificateCredentials.CertificatePem, StringComparison.OrdinalIgnoreCase) + .Replace("%PRIVATE_KEY%", credentials.CertificateCredentials.PrivateKey, StringComparison.OrdinalIgnoreCase) + .Replace("%REGION%", this.configHandler.AWSRegion, StringComparison.OrdinalIgnoreCase) + .Replace("%GREENGRASSCORETOKENEXCHANGEROLEALIAS%", this.configHandler.AWSGreengrassCoreTokenExchangeRoleAliasName, StringComparison.OrdinalIgnoreCase) + .Replace("%THING_NAME%", device.Name, StringComparison.OrdinalIgnoreCase) + .ReplaceLineEndings(); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to Describe Endpoint due to an error in the Amazon IoT API.", e); + } } public Task CreateDeviceWithTwin(string deviceId, bool isEdge, Twin twin, DeviceStatus isEnabled) @@ -580,28 +697,37 @@ public Task CreateEdgeDevice(string deviceId) public async Task RemoveDeviceCredentials(IoTEdgeDevice device) { - var request = new ListThingPrincipalsRequest - { - ThingName = device.DeviceName - }; - - do + try { - var principalResponse = await this.amazonIoTClient.ListThingPrincipalsAsync(request); - - if (!principalResponse.Principals.Any()) + var request = new ListThingPrincipalsRequest { - break; - } + ThingName = device.DeviceName + }; - foreach (var item in principalResponse.Principals) + do { - await RemoveGreengrassCertificateFromPrincipal(device, item); - } - request.NextToken = principalResponse.NextToken; + var principalResponse = await this.amazonIoTClient.ListThingPrincipalsAsync(request); + + if (!principalResponse.Principals.Any()) + { + break; + } + + foreach (var item in principalResponse.Principals) + { + await RemoveGreengrassCertificateFromPrincipal(device, item); + } + + request.NextToken = principalResponse.NextToken; + + } + while (true); + } + catch (AmazonIoTException e) + { + this.logger.LogWarning("Unable to List Thing principal due to an error in the Amazon IoT API.", e); } - while (true); } @@ -609,32 +735,34 @@ private async Task RemoveGreengrassCertificateFromPrincipal(IoTEdgeDevice device { foreach (var item in this.configHandler.AWSGreengrassRequiredRoles) { - _ = await this.amazonIoTClient.AttachPolicyAsync(new AttachPolicyRequest + try { - PolicyName = item, - Target = principalId - }); + _ = await this.amazonIoTClient.AttachPolicyAsync(new AttachPolicyRequest + { + PolicyName = item, + Target = principalId + }); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to attach policy due to an error in the Amazon IoT API.", e); + } } - _ = await this.amazonIoTClient.DetachThingPrincipalAsync(device.DeviceName, principalId); - - _ = await this.amazonSecretsManager.DeleteSecretAsync(new DeleteSecretRequest + try { - ForceDeleteWithoutRecovery = true, - SecretId = device.DeviceName + PublicKeyKey, - }); + _ = await this.amazonIoTClient.DetachThingPrincipalAsync(device.DeviceName, principalId); - _ = await this.amazonSecretsManager.DeleteSecretAsync(new DeleteSecretRequest + } + catch (AmazonIoTException e) { - ForceDeleteWithoutRecovery = true, - SecretId = device.DeviceName + PrivateKeyKey, - }); + throw new Domain.Exceptions.InternalServerErrorException("Unable to detach thing Principal due to an error in the Amazon IoT API.", e); - _ = await this.amazonSecretsManager.DeleteSecretAsync(new DeleteSecretRequest - { - ForceDeleteWithoutRecovery = true, - SecretId = device.DeviceName + CertificateKey, - }); + } + + await DeleteSecret(device.DeviceName + PublicKeyKey); + await DeleteSecret(device.DeviceName + PrivateKeyKey); + await DeleteSecret(device.DeviceName + CertificateKey); var awsPricipalCertRegex = new Regex("/arn:aws:iot:([a-z0-9-]*):(\\d*):cert\\/([0-9a-fA-F]*)/gm"); @@ -647,8 +775,39 @@ private async Task RemoveGreengrassCertificateFromPrincipal(IoTEdgeDevice device var certificateId = matches.Captures[2].Value; - _ = await this.amazonIoTClient.UpdateCertificateAsync(certificateId, CertificateStatus.REGISTER_INACTIVE); - _ = await this.amazonIoTClient.DeleteCertificateAsync(certificateId); + try + { + _ = await this.amazonIoTClient.UpdateCertificateAsync(certificateId, CertificateStatus.REGISTER_INACTIVE); + + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to update certificate due to an error in the Amazon IoT API.", e); + } + try + { + _ = await this.amazonIoTClient.DeleteCertificateAsync(certificateId); + } + catch (AmazonIoTException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to delete certificate due to an error in the Amazon IoT API.", e); + } + } + + private async Task DeleteSecret(string secretId) + { + try + { + _ = await this.amazonSecretsManager.DeleteSecretAsync(new DeleteSecretRequest + { + ForceDeleteWithoutRecovery = true, + SecretId = secretId, + }); + } + catch (AmazonSecretsManagerException e) + { + throw new Domain.Exceptions.InternalServerErrorException("Unable to delete secret due to an error in the Amazon IoT API.", e); + } } } } diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs index 7a2255ab6..7b764c72a 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingTypesJobTests.cs @@ -129,6 +129,8 @@ public async Task Execute_SyncNewAndExistingAndDepprecatedThingTypes_DeviceModel .ReturnsAsync(depcrecatedThingType); _ = this.iaAmazon.Setup(client => client.DeleteThingTypeAsync(It.Is(c => c.ThingTypeName == depcrecatedThingType.ThingTypeName), It.IsAny())) .ReturnsAsync(Fixture.Create); + _ = this.iaAmazon.Setup(client => client.DeleteDynamicThingGroupAsync(It.Is(c => c.ThingGroupName == depcrecatedThingType.ThingTypeName), It.IsAny())) + .ReturnsAsync(Fixture.Create); _ = this.mockExternalDeviceService.Setup(client => client.IsEdgeDeviceModel(It.IsAny())) .ReturnsAsync(false); diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Managers/AwsDeviceModelImageManagerTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Managers/AwsDeviceModelImageManagerTest.cs index 582537744..5ee691698 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Managers/AwsDeviceModelImageManagerTest.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Managers/AwsDeviceModelImageManagerTest.cs @@ -12,14 +12,13 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Managers using Amazon.S3; using Amazon.S3.Model; using AutoFixture; - using Azure; + using FluentAssertions; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Domain.Options; using IoTHub.Portal.Infrastructure.Managers; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; - using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; @@ -121,10 +120,8 @@ public void ChangeDeviceModelImageShouldThrowsAnExceptionForPutObjectResStatusCo _ = this.s3ClientMock.Setup(s3 => s3.PutObjectAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new PutObjectResponse - { - HttpStatusCode = HttpStatusCode.BadGateway - }); + .ThrowsAsync(new AmazonS3Exception("Unable to upload the image in S3 Bucket due to an error in Amazon S3 API.")); + // Assert @@ -133,7 +130,7 @@ public void ChangeDeviceModelImageShouldThrowsAnExceptionForPutObjectResStatusCo // Act _ = await this.awsDeviceModelImageManager.ChangeDeviceModelImageAsync(deviceModelId, imageAsMemoryStream); - }, "Error by uploading the image in S3 Storage"); + }, "Unable to upload the image in S3 Bucket due to an error in Amazon S3 API."); this.s3ClientMock.VerifyAll(); } @@ -157,10 +154,7 @@ public void ChangeDeviceModelImageShouldThrowsAnExceptionForPutACLStatusCode() HttpStatusCode = HttpStatusCode.OK }); _ = this.s3ClientMock.Setup(s3 => s3.PutACLAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new PutACLResponse - { - HttpStatusCode = HttpStatusCode.BadGateway - }); + .ThrowsAsync(new AmazonS3Exception("Unable to set the image access to public and read-only due to an error in Amazon S3 API.")); // Assert _ = Assert.ThrowsAsync(async () => @@ -168,7 +162,7 @@ public void ChangeDeviceModelImageShouldThrowsAnExceptionForPutACLStatusCode() // Act _ = await this.awsDeviceModelImageManager.ChangeDeviceModelImageAsync(deviceModelId, imageAsMemoryStream); - }, "Error by setting the image access to public and read-only"); + }, "Unable to set the image access to public and read-only due to an error in Amazon S3 API."); this.s3ClientMock.VerifyAll(); } @@ -184,13 +178,13 @@ public async Task FailedDeletingDeviceModelImageShouldThrowsAnInternalServerErro _ = this.mockConfigHandler.Setup(handler => handler.AWSBucketName).Returns(bucketName); _ = this.s3ClientMock.Setup(s3 => s3.DeleteObjectAsync(It.IsAny(), It.IsAny())) - .Throws(new RequestFailedException("")); + .ThrowsAsync(new AmazonS3Exception("Unable to delete the image from S3 storage due to an error in Amazon S3 API.")); // Act var act = async () => await this.awsDeviceModelImageManager.DeleteDeviceModelImageAsync(deviceModelId); // Assert - _ = await act.Should().ThrowAsync("Unable to delete the image from the blob storage."); + _ = await act.Should().ThrowAsync("Unable to delete the image from S3 storage due to an error in Amazon S3 API.\""); this.s3ClientMock.VerifyAll(); } @@ -266,10 +260,7 @@ public void SetDefaultImageToModeShouldThrowsAnExceptionForPutObjectResStatusCod _ = this.s3ClientMock.Setup(s3 => s3.PutObjectAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new PutObjectResponse - { - HttpStatusCode = HttpStatusCode.BadGateway - }); + .ThrowsAsync(new AmazonS3Exception("Unable to upload the image in S3 Bucket due to an error in Amazon S3 API.")); // Assert @@ -278,7 +269,7 @@ public void SetDefaultImageToModeShouldThrowsAnExceptionForPutObjectResStatusCod // Act _ = await this.awsDeviceModelImageManager.SetDefaultImageToModel(deviceModelId); - }, "Error by uploading the image in S3 Storage"); + }, "Unable to upload the image in S3 Bucket due to an error in Amazon S3 API."); this.s3ClientMock.VerifyAll(); } @@ -301,10 +292,8 @@ public void SetDefaultImageToModelShouldThrowsAnExceptionForPutACLStatusCode() HttpStatusCode = HttpStatusCode.OK }); _ = this.s3ClientMock.Setup(s3 => s3.PutACLAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new PutACLResponse - { - HttpStatusCode = HttpStatusCode.BadGateway - }); + .ThrowsAsync(new AmazonS3Exception("Unable to set the image access to public and read-only due to an error in Amazon S3 API")); + // Assert _ = Assert.ThrowsAsync(async () => @@ -312,7 +301,7 @@ public void SetDefaultImageToModelShouldThrowsAnExceptionForPutACLStatusCode() // Act _ = await this.awsDeviceModelImageManager.SetDefaultImageToModel(deviceModelId); - }, "Error by setting the image access to public and read-only"); + }, "Unable to set the image access to public and read-only due to an error in Amazon S3 API"); this.s3ClientMock.VerifyAll(); } @@ -358,10 +347,7 @@ public void InitializeDefaultImageBlobShouldThrowsAnExceptionForPutObjectResStat _ = this.mockConfigHandler.Setup(handler => handler.StorageAccountDeviceModelImageMaxAge).Returns(Fixture.Create()); _ = this.s3ClientMock.Setup(s3 => s3.PutObjectAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new PutObjectResponse - { - HttpStatusCode = HttpStatusCode.BadGateway - }); + .ThrowsAsync(new AmazonS3Exception("Unable to set the image access to public and read-only due to an error in Amazon S3 API.")); // Assert @@ -370,7 +356,7 @@ public void InitializeDefaultImageBlobShouldThrowsAnExceptionForPutObjectResStat // Act await this.awsDeviceModelImageManager.InitializeDefaultImageBlob(); - }, "Error by uploading the image in S3 Storage"); + }, "Unable to upload the image in S3 Bucket due to an error in Amazon S3 API."); this.s3ClientMock.VerifyAll(); } @@ -389,10 +375,7 @@ public void InitializeDefaultImageBlobShouldThrowsAnExceptionForPutACLStatusCode HttpStatusCode = HttpStatusCode.OK }); _ = this.s3ClientMock.Setup(s3 => s3.PutACLAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new PutACLResponse - { - HttpStatusCode = HttpStatusCode.BadGateway - }); + .ThrowsAsync(new AmazonS3Exception("Unable to set the image access to public and read-only due to an error in Amazon S3 API.")); // Assert _ = Assert.ThrowsAsync(async () => @@ -400,7 +383,7 @@ public void InitializeDefaultImageBlobShouldThrowsAnExceptionForPutACLStatusCode // Act await this.awsDeviceModelImageManager.InitializeDefaultImageBlob(); - }, "Error by setting the image access to public and read-only"); + }, "Unable to set the image access to public and read-only due to an error in Amazon S3 API."); this.s3ClientMock.VerifyAll(); } diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs index ee5aac341..bf7e16ae0 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDevicePropertyServiceTests.cs @@ -104,10 +104,7 @@ public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenHttpSt .ReturnsAsync(device); _ = this.mockAmazonIotDataClient.Setup(iotDataClient => iotDataClient.GetThingShadowAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new GetThingShadowResponse - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIotDataException("Unable to get the thing shadow due to an error in the Amazon IoT API")); // Act var act = () => this.awsDevicePropertyService.GetProperties(device.Id); @@ -272,10 +269,8 @@ public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenHttpSt .ReturnsAsync(Enumerable.Empty()); _ = this.mockAmazonIotDataClient.Setup(iotDataClient => iotDataClient.UpdateThingShadowAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new UpdateThingShadowResponse - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIotDataException("Unable to upadate the thing shadow due to an error in the Amazon IoT API")); + // Act var act = () => this.awsDevicePropertyService.SetProperties(device.Id, null); diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs index 688594de6..f61d9fef0 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AWSDeviceServiceTests.cs @@ -131,10 +131,7 @@ public async Task CreateDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo }; _ = this.mockAmazonIotClient.Setup(service => service.CreateThingAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new CreateThingResponse() - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIoTException(It.IsAny())); //Act var result = () => this.awsDeviceService.CreateDevice(deviceDto); @@ -161,10 +158,7 @@ public async Task CreateDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo }); _ = this.mockAmazonIotDataClient.Setup(service => service.UpdateThingShadowAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new UpdateThingShadowResponse() - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIotDataException(It.IsAny())); //Act var result = () => this.awsDeviceService.CreateDevice(deviceDto); @@ -270,10 +264,7 @@ public async Task UpdateDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo }; _ = this.mockAmazonIotClient.Setup(service => service.UpdateThingAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new UpdateThingResponse() - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIoTException(It.IsAny())); // Act var result = () => this.awsDeviceService.UpdateDevice(deviceDto); @@ -450,10 +441,7 @@ public async Task DeleteDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo .ReturnsAsync(device); _ = this.mockAmazonIotClient.Setup(service => service.ListThingPrincipalsAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new ListThingPrincipalsResponse() - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIoTException(It.IsAny())); _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(deviceDto.DeviceID, d => d.Tags, d => d.Labels)) .ReturnsAsync(device); @@ -473,15 +461,14 @@ public async Task DeleteDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) .Returns(Task.CompletedTask); // Act - var result = () => this.awsDeviceService.DeleteDevice(deviceDto.DeviceID); + await this.awsDeviceService.DeleteDevice(deviceDto.DeviceID); // Assert - _ = await result.Should().ThrowAsync(); MockRepository.VerifyAll(); } [Test] - public async Task DeleteDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNotOKForDetachThingPrincipal() + public async Task DeleteDeviceShouldThrowExceptionIfFailedWhenDetachThingPrincipal() { // Arrange var deviceDto = new DeviceDetails @@ -507,13 +494,12 @@ public async Task DeleteDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo Fixture.Create() }, HttpStatusCode = HttpStatusCode.OK - }); ; + }); _ = this.mockAmazonIotClient.Setup(service => service.DetachThingPrincipalAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DetachThingPrincipalResponse() - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIoTException(It.IsAny())); + _ = this.mockAmazonIotClient.Setup(service => service.DeleteThingAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new AmazonIoTException(It.IsAny())); _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(deviceDto.DeviceID, d => d.Tags, d => d.Labels)) .ReturnsAsync(device); @@ -534,10 +520,9 @@ public async Task DeleteDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo .Returns(Task.CompletedTask); // Act - var result = () => this.awsDeviceService.DeleteDevice(deviceDto.DeviceID); + await this.awsDeviceService.DeleteDevice(deviceDto.DeviceID); // Assert - _ = await result.Should().ThrowAsync(); MockRepository.VerifyAll(); } @@ -577,10 +562,7 @@ public async Task DeleteDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo }); _ = this.mockAmazonIotClient.Setup(service => service.DeleteThingAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new DeleteThingResponse() - { - HttpStatusCode = HttpStatusCode.BadRequest - }); + .ThrowsAsync(new AmazonIoTException(It.IsAny())); _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(deviceDto.DeviceID, d => d.Tags, d => d.Labels)) .ReturnsAsync(device); @@ -601,10 +583,9 @@ public async Task DeleteDeviceShouldThrowInternalServerErrorIfHttpStatusCodeIsNo .Returns(Task.CompletedTask); // Act - var result = () => this.awsDeviceService.DeleteDevice(deviceDto.DeviceID); + await this.awsDeviceService.DeleteDevice(deviceDto.DeviceID); // Assert - _ = await result.Should().ThrowAsync(); MockRepository.VerifyAll(); } diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsExternalDeviceServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsExternalDeviceServiceTests.cs index 6ecef334e..dea337328 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsExternalDeviceServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsExternalDeviceServiceTests.cs @@ -174,7 +174,26 @@ public async Task DeleteDeviceShouldDeleteAWSCoreDevice() } [Test] - public async Task WhenThingNotExistsOrCoreNotExistsDeviceDeleteShouldPass() + public async Task WhenThingNotExistsAndCoreReturnBadRequestDeviceDeleteShouldPass() + { + // Arrange + var deviceId = Fixture.Create(); + + _ = this.mockGreengrassV2.Setup(c => c.DeleteCoreDeviceAsync(It.Is(r => r.CoreDeviceThingName == deviceId), It.IsAny())) + .ReturnsAsync(new DeleteCoreDeviceResponse { HttpStatusCode = HttpStatusCode.BadRequest }); + + _ = this.mockAmazonIot.Setup(c => c.DeleteThingAsync(It.Is(r => r.ThingName == deviceId), It.IsAny())) + .ThrowsAsync(new Amazon.IoT.Model.ResourceNotFoundException(Fixture.Create())); + + // Act + await this.externalDeviceService.DeleteDevice(deviceId); + + // Assert + this.MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenThingExistsAndCoreDoesNotExistDeviceDeleteShouldPass() { // Arrange var deviceId = Fixture.Create(); @@ -182,6 +201,26 @@ public async Task WhenThingNotExistsOrCoreNotExistsDeviceDeleteShouldPass() _ = this.mockGreengrassV2.Setup(c => c.DeleteCoreDeviceAsync(It.Is(r => r.CoreDeviceThingName == deviceId), It.IsAny())) .ThrowsAsync(new Amazon.GreengrassV2.Model.ResourceNotFoundException(Fixture.Create())); + _ = this.mockAmazonIot.Setup(c => c.DeleteThingAsync(It.Is(r => r.ThingName == deviceId), It.IsAny())) + .ReturnsAsync(new DeleteThingResponse { HttpStatusCode = HttpStatusCode.NoContent }); + + // Act + await this.externalDeviceService.DeleteDevice(deviceId); + + // Assert + + this.MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenThingNotExistsAndCoreDoesExistDeviceDeleteShouldPass() + { + // Arrange + var deviceId = Fixture.Create(); + + _ = this.mockGreengrassV2.Setup(c => c.DeleteCoreDeviceAsync(It.Is(r => r.CoreDeviceThingName == deviceId), It.IsAny())) + .ReturnsAsync(new DeleteCoreDeviceResponse { HttpStatusCode = HttpStatusCode.NoContent }); + _ = this.mockAmazonIot.Setup(c => c.DeleteThingAsync(It.Is(r => r.ThingName == deviceId), It.IsAny())) .ThrowsAsync(new Amazon.IoT.Model.ResourceNotFoundException(Fixture.Create())); @@ -189,6 +228,7 @@ public async Task WhenThingNotExistsOrCoreNotExistsDeviceDeleteShouldPass() await this.externalDeviceService.DeleteDevice(deviceId); // Assert + this.MockRepository.VerifyAll(); }