Skip to content

Commit

Permalink
Add AWS Greengrass deployment support (#2081)
Browse files Browse the repository at this point in the history
* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* frontend mask some EdgeModel modules and commands

* hide containerOption field

* rebase main-vnext

* merge

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* adding thing group

* remove greengras variable

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* rebase main-vnext

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* adding thing group

* Fix minor issues

* Fix Rollout edge deployment with creating thing type + thing group dynamically

* test passed

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* frontend mask some EdgeModel modules and commands

* hide containerOption field

* rebase main-vnext

* merge

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* adding thing group

* remove greengras variable

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* rebase main-vnext

* Create GreenGrass Deployment

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* just for testing componeent creation

* deployment work => Endpoint tested

* greengrass Service & controller tested

* bacckend tests OK

* adding thing group

* Fix minor issues

* Fix Rollout edge deployment with creating thing type + thing group dynamically

* test passed

* refacto and add update edge model part for AWS

* fix failed test

* Minor fixes

---------

Co-authored-by: Kevin BEAUGRAND <contact@kbeaugrand.fr>
  • Loading branch information
ssgueye2 and kbeaugrand committed Jun 20, 2023
1 parent e027d60 commit ac526c9
Show file tree
Hide file tree
Showing 12 changed files with 719 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public interface IEdgeModelService
Task<IoTEdgeModel> GetEdgeModel(string modelId);

Task CreateEdgeModel(IoTEdgeModel edgeModel);

Task UpdateEdgeModel(IoTEdgeModel edgeModel);

Task DeleteEdgeModel(string edgeModelId);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
@using AzureIoTHub.Portal.Models.v10
@using AzureIoTHub.Portal.Shared.Models.v10
@using AzureIoTHub.Portal.Models.v10
@using AzureIoTHub.Portal.Shared.Constants

@inject PortalSettings Portal

<MudDialog DisableSidePadding="true">
<DialogContent>
Expand All @@ -21,16 +25,21 @@
For="@(() => Module.ImageURI)"
Required="true"/>
</MudItem>
<MudItem xs="12">
<MudTextField @bind-Value="@currentContainerCreateOptions"
id=@nameof(IoTEdgeModule.ContainerCreateOptions)

@if (Portal.CloudProvider.Equals(CloudProviders.Azure))
{
<MudItem xs="12">
<MudTextField @bind-Value="@currentContainerCreateOptions"
id=@nameof(IoTEdgeModule.ContainerCreateOptions)
Label="Container Create Options"
Variant="Variant.Outlined"
For="@(() => Module.ContainerCreateOptions)"/>
</MudItem>
<MudItem xs="12">
<MudAlert Severity="Severity.Info"><MudLink Href="https://learn.microsoft.com/en-us/azure/iot-edge/how-to-use-create-options" Target="_blank ">How to configure container create options for IoT Edge modules</MudLink></MudAlert>
</MudItem>
For="@(() => Module.ContainerCreateOptions)" />
</MudItem>
<MudItem xs="12">
<MudAlert Severity="Severity.Info"><MudLink Href="https://learn.microsoft.com/en-us/azure/iot-edge/how-to-use-create-options" Target="_blank ">How to configure container create options for IoT Edge modules</MudLink></MudAlert>
</MudItem>
}

</MudGrid>

<MudTabs Elevation="1" Class="mt-10" Rounded="true" PanelClass="mt-6">
Expand All @@ -39,13 +48,16 @@
<ModuleDialogTab1 EnvironmentVariables="@currentEnvironmentVariables"/>
</MudTabPanel>

<MudTabPanel Text="Module identity twin settings">
<ModuleDialogTab2 ModuleIdentityTwinSettings="@currentModuleIdentityTwinSettings"/>
</MudTabPanel>
@if (Portal.CloudProvider.Equals(CloudProviders.Azure))
{
<MudTabPanel Text="Module identity twin settings">
<ModuleDialogTab2 ModuleIdentityTwinSettings="@currentModuleIdentityTwinSettings" />
</MudTabPanel>

<MudTabPanel Text="Commands">
<ModuleDialogTab3 Commands="@currentCommands"/>
</MudTabPanel>
<MudTabPanel Text="Commands">
<ModuleDialogTab3 Commands="@currentCommands" />
</MudTabPanel>
}

</MudTabs>
</MudContainer>
Expand Down
235 changes: 235 additions & 0 deletions src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AwsConfigService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// 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 AzureIoTHub.Portal.Application.Services;
using AzureIoTHub.Portal.Domain.Exceptions;
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;

public AwsConfigService(
IAmazonGreengrassV2 greengras,
IAmazonIoT iotClient)
{
this.greengras = greengras;
this.iotClient = iotClient;
}

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.");

}
}

private async Task<string> GetThingGroupArn(IoTEdgeModel edgeModel)
{
await CreateThingTypeIfNotExists(edgeModel!.ModelId);

var dynamicThingGroup = new DescribeThingGroupRequest
{
ThingGroupName = edgeModel?.ModelId
};

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!.ModelId,
QueryString = $"thingTypeName: {edgeModel!.ModelId}"
});

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<Tag>
{
new Tag
{
Key = "iotEdge",
Value = "True"
}
}
});
}
}

private async Task<Dictionary<string, ComponentDeploymentSpecification>> CreateGreenGrassComponents(IoTEdgeModel edgeModel)
{
var listcomponentName = new Dictionary<string, ComponentDeploymentSpecification>();
foreach (var component in edgeModel.EdgeModules)
{
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 != System.Net.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 = "1.0.0" });
}

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", "1.0.0"),
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<IEnumerable<Configuration>> GetIoTEdgeConfigurations()
{
throw new NotImplementedException();
}

public Task<IEnumerable<Configuration>> GetDevicesConfigurations()
{
throw new NotImplementedException();
}

public Task RollOutDeviceModelConfiguration(string modelId, Dictionary<string, object> desiredProperties)
{
throw new NotImplementedException();
}

public Task DeleteDeviceModelConfigurationByConfigurationNamePrefix(string configurationNamePrefix)
{
throw new NotImplementedException();
}

public Task RollOutDeviceConfiguration(string modelId, Dictionary<string, object> desiredProperties, string configurationId, Dictionary<string, string> targetTags, int priority = 0)
{
throw new NotImplementedException();
}

public Task<Configuration> GetConfigItem(string id)
{
throw new NotImplementedException();
}

public Task DeleteConfiguration(string configId)
{
throw new NotImplementedException();
}

public Task<int> GetFailedDeploymentsCount()
{
throw new NotImplementedException();
}

public Task<List<IoTEdgeModule>> GetConfigModuleList(string modelId)
{
// To be implemented with the update method in EdgeModelService
throw new NotImplementedException();
}

public Task<List<EdgeModelSystemModule>> GetModelSystemModule(string modelId)
{
throw new NotImplementedException();
}

public Task<List<IoTEdgeRoute>> GetConfigRouteList(string modelId)
{
throw new NotImplementedException();
}

}
}
Loading

0 comments on commit ac526c9

Please sign in to comment.