Créer des connecteurs Akri dans VS Code

Cet article explique comment générer, valider, déboguer et publier des connecteurs Akri personnalisés à l’aide des connecteurs Azure IoT Operations Akri en préversion de l’extension VS Code.

L’extension prend en charge les plateformes suivantes :

  • Linux
  • Sous-système Microsoft Windows pour Linux (WSL)
  • Fenêtres

L’extension vous permet de créer des connecteurs à l’aide des langages de programmation suivants :

  • .NET
  • Rust

Prerequisites

Environnement de développement :

Configuration docker :

  • Les images utilisées par l’extension doivent être extraites et étiquetées localement avant d’utiliser l’extension :

    docker pull mcr.microsoft.com/azureiotoperations/devx-runtime:0.1.8
    docker tag mcr.microsoft.com/azureiotoperations/devx-runtime:0.1.8 devx-runtime
    
  • Tous les conteneurs que l’extension lance sont configurés pour s’exécuter sur un réseau personnalisé nommé aio_akri_network à des fins d’isolation réseau :

    docker network create aio_akri_network
    
  • Le conteneur DevX utilise un volume akri_devx_docker_volume personnalisé pour stocker la configuration du cluster :

    docker volume rm akri_devx_docker_volume # delete the volume created from any previous release
    docker volume create akri_devx_docker_volume
    

Pour déployer et utiliser votre connecteur avec une instance Azure IoT Operations, vous avez également besoin des éléments suivants :

  • Une instance d’Azure IoT Operations.
  • Accès à un registre de conteneurs, tel qu’Azure Container Registry, pour publier vos images de connecteur.
  • Un point de terminaison de registre de conteneurs configuré dans votre instance Opérations Azure IoT pour extraire vos images de connecteur. Pour plus d’informations, consultez Configurer des points de terminaison de Registre.

Créer et valider un connecteur Akri

Dans cet exemple, vous créez un connecteur HTTP/REST à l’aide du langage C#, générez une image Docker, puis exécutez l’application de connecteur à l’aide de l’extension VS Code :

  1. Appuyez Ctrl+Shift+P pour ouvrir la palette de commandes et rechercher la commande Azure IoT Operations Akri Connectors: Create a New Akri Connector. Créez un dossier appelé my-connectors et sélectionnez-le, sélectionnez C# comme langue, entrez un nom pour le connecteur tel rest_connectorque , puis sélectionnez PollingTelemetryConnector comme type de connecteur.

  2. L’extension crée un espace de travail nommé à l’aide du nom du connecteur que vous avez choisi à l’étape précédente. L’espace de travail inclut la structure d’un connecteur de télémétrie de sondage écrit dans le langage C#.

Les étapes suivantes supposent que vous avez créé un projet .NET appelé MyConnector.

Important

L’exemple de code suivant est destiné uniquement à des fins d’illustration et n’est pas destiné à être utilisé en production. Dans un connecteur de production, vous devez implémenter une logique robuste de gestion des erreurs et de nouvelle tentative, et vous assurer que toutes les informations d’identification utilisées pour se connecter à la ressource sont stockées et utilisées en toute sécurité. Un connecteur de qualité de production doit implémenter le contrat décrit dans le document du contrat d’opérateur et de connecteur Akri dans le référentiel des kits SDK.

Pour représenter l’état du thermostat, créez un fichier appelé ThermostatStatus.cs dans le dossier de l’espace MyConnector de travail avec le contenu suivant. Ce fichier modélise la réponse JSON à partir du point de terminaison REST :

using System.Text.Json.Serialization;

namespace MyConnector
{
    internal class ThermostatStatus
    {
        [JsonPropertyName("desiredTemperature")]
        public double? DesiredTemperature { get; set; }

        [JsonPropertyName("currentTemperature")]
        public double? CurrentTemperature { get; set; }
    }
}    

Pour ajouter un paramètre de configuration de point de données, créez un fichier appelé DataPointConfiguration.cs dans le dossier de l’espace MyConnector de travail avec le contenu suivant. Ce fichier modélise un paramètre de configuration pour un point de données que l’utilisateur spécifie dans l’interface utilisateur de l’expérience des opérations :

using System.Text.Json.Serialization;

namespace MyConnector
{
    public class DataPointConfiguration
    {
        [JsonPropertyName("HttpRequestMethod")]
        public string? HttpRequestMethod { get; set; }
    }
} 

Implémentez la SampleDatasetAsync méthode dans la classe fournie DatasetSampler . La méthode prend un Dataset paramètre. Un Dataset contient les points de données du connecteur à traiter.

  1. Ouvrez le fichier MyConnector/DatasetSampler.cs dans votre espace de travail VS Code.

  2. Pour transmettre les données requises pour le traitement des données de point de terminaison, ajoutez un constructeur à la DatasetSampler classe. La classe utilise les HttpClient et EndpointProfileCredentials pour se connecter et s’authentifier avec le point de terminaison de ressource :

    private readonly HttpClient _httpClient;
    private readonly string _assetName;
    private readonly EndpointCredentials? _credentials;
    
    private readonly static JsonSerializerOptions _jsonSerializerOptions = new()
    {
        AllowTrailingCommas = true,
    };
    
    public DatasetSampler(HttpClient httpClient, string assetName, EndpointCredentials? credentials)
    {
        _httpClient = httpClient;
        _assetName = assetName;
        _credentials = credentials;
    }
    
    public ValueTask DisposeAsync()
    {
        _httpClient.Dispose();
        return ValueTask.CompletedTask;
    }
    
  3. Modifiez la GetSamplingIntervalAsync méthode pour retourner un intervalle d’échantillonnage de trois secondes :

    public Task<TimeSpan> GetSamplingIntervalAsync(AssetDataset dataset, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(TimeSpan.FromSeconds(3));
    }
    

    Note

    Par souci de simplicité, cet exemple utilise un intervalle d’échantillonnage fixe. Dans un connecteur de production, vous pouvez configurer l’intervalle d’échantillonnage à l’aide des métadonnées du connecteur pour définir une propriété d’intervalle d’échantillonnage qu’un utilisateur peut définir dans l’interface utilisateur de l’expérience des opérations.

  4. Remplacez la méthode existante SampleDatasetAsync par le plan suivant :

    public async Task<byte[]> SampleDatasetAsync(AssetDataset dataset, CancellationToken cancellationToken = default)
    {
        int retryCount = 0;
        while (true)
        {
            try
            {
                // TODO: Implement your dataset sampling logic here.
            }
            catch (Exception ex)
            {
                if (++retryCount >= 3)
                {
                    throw new InvalidOperationException($"Failed to sample dataset with name {dataset.Name} in asset with name {_assetName}. Error: {ex.Message}", ex);
                }
                await Task.Delay(1000, cancellationToken);
            }
        }
    }
    
  5. Dans le bloc try de la méthode SampleDatasetAsync, ajoutez le code suivant pour récupérer chaque DataPoint du DataSet et extraire les chemins de la source de données. Ces chemins font partie des URL utilisées pour extraire les données du point de terminaison REST. Les points de données currentTemperature et desiredTemperature ont été modélisés précédemment dans la classe ThermostatStatus. La méthode de requête HTTP est extraite de la configuration du point de données modélisée dans la DataPointConfiguration classe :

    AssetDatasetDataPointSchemaElement httpServerDesiredTemperatureDataPoint = dataset.DataPoints!.Where(x => x.Name!.Equals("desiredTemperature"))!.First();
    HttpMethod httpServerDesiredTemperatureHttpMethod = HttpMethod.Parse(JsonSerializer.Deserialize<DataPointConfiguration>(httpServerDesiredTemperatureDataPoint.DataPointConfiguration!, _jsonSerializerOptions)!.HttpRequestMethod);
    string httpServerDesiredTemperatureRequestPath = httpServerDesiredTemperatureDataPoint.DataSource!;
    
    AssetDatasetDataPointSchemaElement httpServerCurrentTemperatureDataPoint = dataset.DataPoints!.Where(x => x.Name!.Equals("currentTemperature"))!.First();
    HttpMethod httpServerCurrentTemperatureHttpMethod = HttpMethod.Parse(JsonSerializer.Deserialize<DataPointConfiguration>(httpServerCurrentTemperatureDataPoint.DataPointConfiguration!, _jsonSerializerOptions)!.HttpRequestMethod);
    string httpServerCurrentTemperatureRequestPath = httpServerCurrentTemperatureDataPoint.DataSource!;
    

    Note

    Par souci de simplicité, cet exemple montre uniquement comment récupérer la méthode HTTP à utiliser à partir de la configuration du point de données. L’exemple n’utilise pas cette valeur lorsqu’il effectue la requête HTTP.

  6. Dans la même méthode, configurez l’authentification à l’aide des informations d’identification fournies si les points de terminaison authentifiés sont en cours d’utilisation :

    if (_credentials != null && _credentials.Username != null && _credentials.Password != null)
    {
        // Note that this sample uses username + password for authenticating the connection to the asset. In general,
        // x509 authentication should be used instead (if available) as it is more secure.
        string httpServerUsername = _credentials.Username;
        string httpServerPassword = _credentials.Password;
        var byteArray = Encoding.ASCII.GetBytes($"{httpServerUsername}:{httpServerPassword}");
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
    }
    

    Ce code extrait les informations d’identification et les ajoute à l’en-tête d’autorisation. L’authentification DatasetSampler de base est implémentée avec les informations d’identification de nom d’utilisateur et de mot de passe.

  7. Ensuite, ajoutez du code pour effectuer une requête HTTP au point de terminaison, désérialiser la réponse, et extraire les propriétés CurrentTemperature et DesiredTemperature, puis les placer dans un objet ThermostatStatus.

    var currentTemperatureHttpResponse = await _httpClient.GetAsync(httpServerCurrentTemperatureRequestPath);
    var desiredTemperatureHttpResponse = await _httpClient.GetAsync(httpServerDesiredTemperatureRequestPath);
    
    if (currentTemperatureHttpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized
        || desiredTemperatureHttpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        throw new Exception("Failed to authorize request to HTTP server. Check credentials configured in rest-server-device-definition.yaml.");
    }
    
    currentTemperatureHttpResponse.EnsureSuccessStatusCode();
    desiredTemperatureHttpResponse.EnsureSuccessStatusCode();
    
    ThermostatStatus thermostatStatus = new()
    {
        CurrentTemperature = (JsonSerializer.Deserialize<ThermostatStatus>(await currentTemperatureHttpResponse.Content.ReadAsStreamAsync(), _jsonSerializerOptions)!).CurrentTemperature,
        DesiredTemperature = (JsonSerializer.Deserialize<ThermostatStatus>(await desiredTemperatureHttpResponse.Content.ReadAsStreamAsync(), _jsonSerializerOptions)!).DesiredTemperature
    };
    
  8. Ensuite, sérialisez l’état au format JSON et retournez la réponse au point de terminaison. Dans cet exemple, la charge utile de réponse HTTP correspond déjà au schéma de message attendu. Par conséquent, aucune traduction n’est nécessaire :

    // The HTTP response payload matches the expected message schema, so return it as-is
    return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(thermostatStatus));
    

    Conseil / Astuce

    Si vous le souhaitez, le connecteur peut inscrire un schéma dans le registre de schémas pour permettre à d’autres opérations Azure IoT de comprendre le format des messages.

  9. Enfin, importez les types nécessaires :

    using Azure.Iot.Operations.Connector.Files;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Text.Json;
    

La version finale du code ressemble à DatasetSampler.

Implémentez la CreateDatasetSampler méthode dans la DatasetSamplerProvider classe. Cette classe crée des DataSetSampler objets à injecter dans l’application en fonction des besoins.

  1. Ouvrez le MyConnector/DatasetSamplerProvider.cs fichier dans votre espace de travail VS Code.

  2. Dans la méthode CreateDatasetSampler, retournez un DatasetSampler avec le endpointCredentials si le nom du jeu de données est thermostat_status:

    if (dataset.Name.Equals("thermostat_status"))
    {
        if (device.Endpoints != null
            && device.Endpoints.Inbound != null
            && device.Endpoints.Inbound.TryGetValue(inboundEndpointName, out var inboundEndpoint))
        {
            var httpClient = new HttpClient()
            {
                BaseAddress = new Uri(inboundEndpoint.Address),
            };
    
            return new DatasetSampler(httpClient, assetName, endpointCredentials);
        }
    }
    
    throw new InvalidOperationException($"Unrecognized dataset with name {dataset.Name} on asset with name {assetName}");
    

    Note

    Par souci de simplicité, cet exemple suppose que le nom du jeu de données est toujours thermostat_status. Dans un connecteur de production, vous pouvez implémenter une logique supplémentaire pour gérer plusieurs jeux de données.

La version finale du code ressemble à DatasetSamplerProvider.

Ensuite, générez le projet pour confirmer qu’il n’y a aucune erreur. Utilisez la commande VS Code Azure IoT Operations Akri Connectors : générez un connecteur Akri et choisissez le mode Mise en production . Cette commande affiche la progression de la compilation dans la console OUTPUT et vous avertit une fois la compilation terminée. Vous pouvez ensuite voir une nouvelle image Docker nommée <connector_name> avec une balise release localement dans Docker Desktop.

Pour tester le nouveau connecteur localement, procédez comme suit :

  1. Créez un point de terminaison local qui joue le rôle de serveur REST pour que le connecteur se connecte. Dans le explore-iot-operation référentiel que vous avez cloné précédemment, exécutez les commandes suivantes pour générer un serveur REST local à des fins de test :

    cd samples/akri-vscode-extension/sample-rest-server
    docker build -t rest-server:latest .
    

    Vous pouvez voir l’image dans Docker Desktop.

  2. Exécutez la commande suivante pour démarrer le serveur REST dans un conteneur local :

    docker run -d --rm --network aio_akri_network --name restserver rest-server:latest
    
  3. Vous pouvez voir le conteneur s’exécutant dans Docker Desktop. Le serveur REST est accessible à http://restserver:3000 pour les conteneurs s'exécutant sur aio_akri_network.

  4. Copiez le fichier rest-server-device-definition.yaml à partir du samples/akri-vscode-extension/rest-server-custom-resources dossier dans votre copie locale du explore-iot-operations dépôt vers le dossier Appareils de votre espace de travail connecteur dans VS Code. Cette ressource d’appareil définit une connexion de point de terminaison au serveur REST.

  5. Copiez le fichier rest-server-asset1-definition.yaml à partir du samples/akri-vscode-extension/rest-server-custom-resources dossier dans votre copie locale du explore-iot-operations dépôt vers le dossier Assets de votre espace de travail connecteur dans VS Code. Cette ressource publie les informations de température de l’appareil dans la mqtt/machine/asset1/status rubrique MQTT.

  6. Copiez le fichier rest-server-asset2-definition.yaml à partir du samples/akri-vscode-extension/rest-server-custom-resources dossier dans votre copie locale du explore-iot-operations dépôt vers le dossier Assets de votre espace de travail connecteur dans VS Code. Cette ressource publie les informations de température de l’appareil dans le stockage d’état.

  7. Pour tester le connecteur avec les ressources de l’appareil et de la ressource, accédez au panneau Exécuter et déboguer dans l’espace de travail VS Code, puis sélectionnez la configuration Exécuter un connecteur Akri . Cette configuration lance un terminal qui exécute les tâches de prélancement pour démarrer le aio-broker conteneur et le connecteur REST que vous avez développé dans un autre conteneur appelé <connector_name>_release. Cette opération prend plusieurs minutes. Vous pouvez voir le flux de données de télémétrie du serveur REST vers le répartiteur MQ via le connecteur REST dans la fenêtre de terminal dans VS Code. Les journaux de conteneur sont également visibles dans Docker Desktop.

  8. Vous pouvez arrêter l’exécution à tout moment à l’aide du bouton Arrêter dans le panneau de commandes de débogage. Cette commande nettoie et supprime les conteneurs aio-broker en cours d’exécution et <connector_name>_release.

Déboguer un connecteur Akri

Pour déboguer un connecteur Akri basé sur .NET, vérifiez que l’extension VS Code C# est installée. Utilisez le même connecteur REST que vous avez créé précédemment :

  1. Pour générer le connecteur en mode Débogage , utilisez la commande VS Code Azure IoT Operations Akri Connectors : générez un connecteur Akri et sélectionnez Mode débogage . Cette commande crée une image Docker locale appelée <connector_name> avec la balise debug. Vous pouvez voir l’image dans Docker Desktop.

  2. Vous pouvez ajouter un point d’arrêt et l’exécution s’arrête lorsque le point d’arrêt est atteint. Essayez d’ajouter un point d’arrêt au début de la SampleDatasetAsync méthode dans DatasetSampler.cs.

  3. Pour déboguer le connecteur, accédez au panneau Exécuter et Déboguer dans l’espace de travail VS Code, puis sélectionnez la configuration du connecteur Akri. Cette configuration lance un terminal qui exécute les tâches de prélancement pour démarrer le aio-broker conteneur et le connecteur REST que vous avez développé dans un autre conteneur appelé <connector_name>_debug. Cette opération prend plusieurs minutes. Vous pouvez voir le flux de données de télémétrie du serveur REST vers le répartiteur MQ via le connecteur REST dans la fenêtre de terminal dans VS Code. Les journaux de conteneur sont également visibles dans Docker Desktop.

  4. Utilisez le bouton Déconnecter dans le panneau de commandes de débogage pour mettre fin à l’exécution.

Note

L’extension AKri VS Code lance le conteneur DevX dans un scénario d’exécution/débogage avec une période d’expiration de trois minutes. Si le conteneur ne termine pas le lancement au cours de la période d’expiration, l’extension met fin au conteneur.

Appliquer des mises à jour de configuration

Vous pouvez mettre à jour dynamiquement les configurations de l’appareil et des ressources dans l’environnement d’exécution local pendant que vous exécutez votre connecteur. Cette fonctionnalité vous permet de vérifier que votre connecteur répond aux modifications de configuration. Utilisez les commandes d’extension VS Code suivantes pour apporter ces modifications :

  • Connecteurs Azure IoT Operations Akri : appliquer le YAML de périphérique au cluster
  • Connecteurs Azure IoT Operations Akri : appliquer le YAML de ressource au cluster
  • Connecteurs Azure IoT Operations Akri : supprimer le fichier YAML du dispositif du cluster
  • Connecteurs Azure IoT Operations Akri : supprimer le fichier YAML de ressource du cluster

Capturer l’état du connecteur

Pour capturer l’état actuel du registre de schémas, utilisez la commande d’extension Azure IoT Operations Akri Connector : Capture Connector State VS Code. Cette commande crée un dossier dans le dossier OUTPUT de l’espace de travail avec un nom basé sur l’horodatage actuel. Le dossier créé contient une copie de l’état actuel du registre de schémas, y compris les schémas créés par le connecteur personnalisé.

L’état du registre de schémas est toujours visible dans le Output/ConnectorState dossier. La commande vous permet de capturer l’état du registre de schémas à un moment spécifique.

Publier une image de connecteur

Utilisez les connecteurs Azure IoT Operations Akri : publiez une commande d’image ou de métadonnées du connecteur Akri pour publier des images de connecteur dans un registre ACR (Microsoft Azure Container Registry). La commande utilise les commandes Microsoft Azure CLI et oras. Pour publier dans un registre ACR, vous avez besoin de votre ID d’abonnement Azure et du nom du registre ACR.

Configuration des métadonnées de l'auteur du connecteur

Utilisez l’espace de travail VS Code créé à partir de la commande Créer un connecteur Akri pour créer le connector-metadata.json fichier conforme au schéma JSON pour le schéma Azure IoT Operations Connector Metadata 9.0-preview . Vous pouvez placer ce fichier n’importe où dans l'espace de travail du connecteur. L’extension fournit une fonctionnalité de validation statique à l’aide du connector-metadata.json fichier et affiche des avertissements dans le PROBLEMS panneau si des propriétés requises sont manquantes.

Publier des artéfacts de métadonnées

Utilisez les connecteurs Azure IoT Operations Akri : commande Publier l’image ou les métadonnées du connecteur Akri pour publier des dossiers de métadonnées dans un registre ACR. La commande utilise les commandes Azure CLI et oras. Pour publier dans un registre ACR, vous avez besoin de votre ID d’abonnement Azure et du nom du registre ACR. Actuellement, l’extension s’attend à ce que les fichiers appelés connector-metadata.json et éventuellement additionalConfig.json soient présents dans n’importe quel dossier que vous envoyez.

Problèmes connus

  • Les mises à jour de configuration résultant des Delete/Apply Asset/Device YAML commandes VS Code ne fonctionnent actuellement pas dans Windows en raison des limitations de l’implémentation CIFS dans le noyau Linux. Tous les événements de modification de fichier dans les dossiers montés sur l’hôte ne sont pas propagés au conteneur par Docker pour Windows.

  • Lorsque vous supprimez une ressource ou un appareil du cluster à l’aide des commandes VS Code, le connecteur .NET lève actuellement l’erreur 404 suivante :

    Unhandled exception. Azure.Iot.Operations.Protocol.Retry.RetryExpiredException: Retry expired while attempting the operation. Last known exception is the inner exception.
    ---> Azure.Iot.Operations.Services.AssetAndDeviceRegistry.Models.AkriServiceErrorException: ApiError: assets.namespaces.deviceregistry.microsoft.com "my-rest-thermostat-asset2" not found: NotFound (ErrorResponse { status: "Failure", message: "assets.namespaces.deviceregistry.microsoft.com \"my-rest-thermostat-asset2\" not found", reason: "NotFound", code: 404 })
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.<>c__DisplayClass19_0.<<SetNotificationPreferenceForAssetUpdatesAsync>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.RunWithRetryAsync[TResult](Func`1 taskFunc, CancellationToken cancellationToken)
    --- End of inner exception stack trace ---
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.RunWithRetryAsync[TResult](Func`1 taskFunc, CancellationToken cancellationToken)
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.SetNotificationPreferenceForAssetUpdatesAsync(String deviceName, String inboundEndpointName, String assetName, NotificationPreference notificationPreference, Nullable`1 commandTimeout, CancellationToken cancellationToken)
    at Azure.Iot.Operations.Connector.AdrClientWrapper.AssetFileChanged(Object sender, AssetFileChangedEventArgs e)
    at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
    at System.Threading.ThreadPoolWorkQueue.Dispatch()
    at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
    
  • Actuellement, le lancement de l’image DevX en tant que conteneur à partir de WSL sans Docker Desktop installé entraîne le blocage du conteneur pour toujours.