Skip to content

Commit

Permalink
Merge branch 'main-vnext' into #2077-Get-AWS-Iot-EdgeDeviceDetails
Browse files Browse the repository at this point in the history
  • Loading branch information
ssgueye2 authored Jun 1, 2023
2 parents 5b00c04 + 380be2f commit 6814e22
Show file tree
Hide file tree
Showing 9 changed files with 838 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-documentation-new-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
steps:
- uses: actions/checkout@v3.4.0
with:
ref: docs/main
ref: docs/{{ github.ref_name }}
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
Expand Down
16 changes: 15 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,35 @@ jobs:
- id: docker-tag
uses: yuya-takeyama/docker-tag-from-github-ref-action@v1

- name: Docker Login
- name: Configure AWS credentials
id: aws-credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# Should use us-east-1 region to push to ECR public registry
aws-region: ${{ secrets.AWS_REGION }}

- name: Docker Login to ACR
# You may pin to the exact commit or the version.
uses: docker/login-action@v2.1.0
with:
registry: ${{ env.registry_name }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1

- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
# list of Docker images to use as base name for tags
images: |
${{ env.registry_name }}/${{ github.repository_owner }}/${{ env.image_name }}
${{ steps.login-ecr.outputs.registry }}/${{ env.image_name }}
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ public AWSDeviceThingProfile()
.ForMember(dest => dest.ThingName, opts => opts.MapFrom(src => src.DeviceName))
.ForMember(dest => dest.Payload, opts => opts.MapFrom(src => EmptyPayload()))
.ReverseMap();

_ = CreateMap<DescribeThingResponse, Device>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ThingId))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingName))
.ForMember(dest => dest.Version, opts => opts.MapFrom(src => src.Version))
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Attributes.Select(att => new DeviceTagValue
{
Name = att.Key,
Value = att.Value
})));
}

private static MemoryStream EmptyPayload()
Expand Down
3 changes: 2 additions & 1 deletion src/AzureIoTHub.Portal.Domain/Entities/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace AzureIoTHub.Portal.Domain.Entities
{
using System.Collections.ObjectModel;
using AzureIoTHub.Portal.Domain.Base;

public class Device : EntityBase
Expand Down Expand Up @@ -50,6 +51,6 @@ public class Device : EntityBase
/// <summary>
/// List of custom device tags and their values.
/// </summary>
public ICollection<DeviceTagValue> Tags { get; set; } = default!;
public ICollection<DeviceTagValue> Tags { get; set; } = new Collection<DeviceTagValue>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ apt-get update && \

## Create the greengrass configuration
mkdir -p /etc/greengrass/certs /etc/greengrass/config
useradd --system --create-home ggc_user
groupadd --system ggc_group

cat > /etc/greengrass/certs/device.pem.crt << EOF
%CERTIFICATE%
Expand All @@ -30,21 +28,18 @@ system:
services:
aws.greengrass.Nucleus:
componentType: "NUCLEUS"
version: "nucleus-version"
version: "2.10.1"
configuration:
awsRegion: "%REGION%"
iotRoleAlias: "GreengrassCoreTokenExchangeRoleAlias"
iotDataEndpoint: "%DATA_ENDPOINT%"
iotCredEndpoint: "%CREDENTIALS_ENDPOINT%"
EOF

chown -R ggc_user:ggc_group /etc/greengrass
chmod 640 /etc/greengrass/ -R

## Download the AWS IoT Greengrass Core software
curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-nucleus-latest.zip > greengrass-nucleus-latest.zip
jarsigner -verify -certs -verbose greengrass-nucleus-latest.zip
unzip greengrass-nucleus-latest.zip -d GreengrassInstaller && rm greengrass-nucleus-latest.zip
curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-2.10.1.zip > greengrass.zip
jarsigner -verify -certs -verbose greengrass.zip
unzip greengrass.zip -d GreengrassInstaller && rm greengrass.zip

## Install the AWS IoT Greengrass Core software
java -Droot="/greengrass/v2" -Dlog.store=FILE \
Expand All @@ -53,5 +48,8 @@ java -Droot="/greengrass/v2" -Dlog.store=FILE \
--component-default-user ggc_user:ggc_group \
--setup-system-service true

chown -R ggc_user:ggc_group /etc/greengrass
chmod 640 /etc/greengrass/ -R

## Remove the installer directory
rm -rf ./GreengrassInstaller
rm -rf ./GreengrassInstaller
185 changes: 185 additions & 0 deletions src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright (c) CGI France. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace AzureIoTHub.Portal.Infrastructure.Jobs.AWS
{
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Amazon.IoT;
using Amazon.IoT.Model;
using Amazon.IotData;
using Amazon.IotData.Model;
using AutoMapper;
using AzureIoTHub.Portal.Domain;
using AzureIoTHub.Portal.Domain.Entities;
using AzureIoTHub.Portal.Domain.Repositories;
using Microsoft.Extensions.Logging;
using Quartz;
using Quartz.Util;

[DisallowConcurrentExecution]
public class SyncThingsJob : IJob
{

private readonly ILogger<SyncThingsJob> logger;
private readonly IMapper mapper;
private readonly IUnitOfWork unitOfWork;
private readonly IDeviceRepository deviceRepository;
private readonly IDeviceModelRepository deviceModelRepository;
private readonly IDeviceTagValueRepository deviceTagValueRepository;
private readonly IAmazonIoT amazonIoTClient;
private readonly IAmazonIotData amazonIoTDataClient;

public SyncThingsJob(
ILogger<SyncThingsJob> logger,
IMapper mapper,
IUnitOfWork unitOfWork,
IDeviceRepository deviceRepository,
IDeviceModelRepository deviceModelRepository,
IDeviceTagValueRepository deviceTagValueRepository,
IAmazonIoT amazonIoTClient,
IAmazonIotData amazonIoTDataClient)
{
this.mapper = mapper;
this.unitOfWork = unitOfWork;
this.deviceRepository = deviceRepository;
this.deviceModelRepository = deviceModelRepository;
this.deviceTagValueRepository = deviceTagValueRepository;
this.amazonIoTClient = amazonIoTClient;
this.amazonIoTDataClient = amazonIoTDataClient;
this.logger = logger;
}


public async Task Execute(IJobExecutionContext context)
{
try
{
this.logger.LogInformation("Start of sync Things job");

await SyncThingsAsDevices();

this.logger.LogInformation("End of sync Things job");
}
catch (Exception e)
{
this.logger.LogError(e, "Sync Things job has failed");
}
}

private async Task SyncThingsAsDevices()
{
var things = await GetAllThings();

foreach (var thing in things)
{
//Thing error
if (thing.HttpStatusCode != HttpStatusCode.OK)
{
this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error in the Amazon IoT API : {thing.HttpStatusCode}");
continue;
}

//ThingType not specified
if (thing.ThingTypeName.IsNullOrWhiteSpace())
{
this.logger.LogInformation($"Cannot import device '{thing.ThingName}' since it doesn't have related thing type.");
continue;
}

//ThingType not find in DB
var deviceModel = this.deviceModelRepository.GetByName(thing.ThingTypeName);
if (deviceModel == null)
{
this.logger.LogWarning($"Cannot import device '{thing.ThingName}'. The ThingType '{thing.ThingTypeName}' doesn't exist");
continue;
}

//ThingShadow not specified
var thingShadowRequest = new GetThingShadowRequest()
{
ThingName = thing.ThingName
};
try
{
var thingShadow = await this.amazonIoTDataClient.GetThingShadowAsync(thingShadowRequest);
if (thingShadow.HttpStatusCode != HttpStatusCode.OK)
{
if (thingShadow.HttpStatusCode.Equals(HttpStatusCode.NotFound))
this.logger.LogInformation($"Cannot import device '{thing.ThingName}' since it doesn't have related classic thing shadow");
else
this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT API : {thingShadow.HttpStatusCode}");
continue;
}
}
catch (AmazonIotDataException e)
{
this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT Data API.", e);
continue;
}

//Create or update the thing
await CreateOrUpdateThing(thing, deviceModel);
}

foreach (var item in (await this.deviceRepository.GetAllAsync(
device => !things.Select(x => x.ThingId).Contains(device.Id),
default,
d => d.Tags,
d => d.Labels
)))
{
this.deviceRepository.Delete(item.Id);
}

await this.unitOfWork.SaveAsync();
}

private async Task<List<DescribeThingResponse>> GetAllThings()
{
var things = new List<DescribeThingResponse>();

var response = await amazonIoTClient.ListThingsAsync();

foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName }))
{
try
{
things.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing));
}
catch (AmazonIoTException e)
{
this.logger.LogWarning($"Cannot import device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e);
continue;
}
}

return things;
}

private async Task CreateOrUpdateThing(DescribeThingResponse thing, DeviceModel deviceModel)
{
var device = this.mapper.Map<Device>(thing);
var deviceEntity = await this.deviceRepository.GetByIdAsync(device.Id, d => d.Tags);
device.DeviceModelId = deviceModel.Id;

if (deviceEntity == null)
{
await this.deviceRepository.InsertAsync(device);
}
else
{
if (deviceEntity.Version >= device.Version) return;

foreach (var deviceTagEntity in deviceEntity.Tags)
{
this.deviceTagValueRepository.Delete(deviceTagEntity.Id);
}

_ = this.mapper.Map(device, deviceEntity);
this.deviceRepository.Update(deviceEntity);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,15 @@ private static IServiceCollection ConfigureAWSSyncJobs(this IServiceCollection s
.AddTrigger(t => t
.WithIdentity($"{nameof(SyncThingTypesJob)}")
.ForJob(nameof(SyncThingTypesJob))
.WithSimpleSchedule(s => s
.WithSimpleSchedule(s => s
.WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes)
.RepeatForever()));

_ = q.AddJob<SyncThingsJob>(j => j.WithIdentity(nameof(SyncThingsJob)))
.AddTrigger(t => t
.WithIdentity($"{nameof(SyncThingsJob)}")
.ForJob(nameof(SyncThingsJob))
.WithSimpleSchedule(s => s
.WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes)
.RepeatForever()));

Expand Down
Loading

0 comments on commit 6814e22

Please sign in to comment.