Importer et exporter des identités d’appareil IoT Hub en bloc

Chaque hub IoT a un registre des identités que vous pouvez utiliser pour créer des ressources d'appareil dans le service. Le registre des identités vous permet également de contrôler l’accès aux points de terminaison orientés appareil. Cet article décrit comment importer et exporter en masse des identités de périphériques vers et depuis un registre d'identités, à l'aide de l'exemple ImportExportDeviceSample inclus dans le SDK Microsoft Azure IoT pour .NET. Pour plus d'informations sur l'utilisation de cette fonctionnalité lors de la migration d'un hub IoT vers une autre région, voir Comment migrer manuellement un hub Azure IoT à l'aide d'un modèle Azure Resource Manager.

Remarque

IoT Hub a récemment ajouté la prise en charge des réseaux virtuels dans un nombre limité de régions. Cette fonctionnalité sécurise les opérations d’importation et d’exportation et élimine la nécessité de passer des clés pour l’authentification. Actuellement, la prise en charge des réseaux virtuels est disponible uniquement dans les régions suivantes : WestUS2, EastUSet SouthCentralUS. Pour en savoir plus sur la prise en charge des réseaux virtuels et sur les appels d’API pour l’implémenter, consultez Prise en charge des réseaux virtuels par IoT Hub.

Les opérations d'importation et d'exportation se déroulent dans le cadre de tâches, qui vous permettent d'exécuter des opérations de service en bloc par rapport à un IoT Hub.

La classe RegistryManager du SDK comprend les méthodes ExportDevicesAsync et ImportDevicesAsync qui utilisent le cadre de travail. Ces méthodes vous permettent d’exporter, d’importer et de synchroniser l’intégralité d’un registre des identités IoT hub.

Cette article décrit l'utilisation de la classe RegistryManager et du système de tâches pour effectuer des importations et exportations en bloc d'appareils vers et depuis le registre des identités d'un hub IoT. Vous pouvez également utiliser le service IoT Hub Device Provisioning d'Azure pour activer l'approvisionnement sans contact et JAT vers un ou plusieurs hubs IoT. Pour plus d’informations, consultez la documentation relative au service d’approvisionnement.

Notes

Certains extraits de code de cet article sont inclus dans l’exemple de service ImportExportDevicesSample fourni avec le Kit de développement logiciel (SDK) Microsoft Azure IoT pour .NET. L’exemple se trouve dans le /iothub/service/samples/how to guides/ImportExportDevicesSample dossier du Kit de développement logiciel (SDK) et, le cas échéant, les extraits de code sont inclus dans le ImportExportDevicesSample.cs fichier de cet exemple sdk. Pour plus d’informations sur l’exemple ImportExportDevicesSample et d’autres exemples de service inclus dans le kit de développement logiciel (SDK) Azure IoT for.NET, consultez Exemples de service Azure IoT Hub pour C#.

Que sont les tâches ?

Les opérations de registre des identités utilisent le système de tâches lorsque l'opération fait intervenir les éléments suivants :

  • a un temps d’exécution potentiellement long par rapport à une opération d’exécution standard.

  • renvoie une grande quantité de données à l’utilisateur.

Au lieu d'avoir un seul appel d'API en attente ou qui se bloque sur le résultat de l'opération, cette dernière crée de façon asynchrone une tâche pour cet IoT Hub. L’opération renvoie alors immédiatement un objet JobProperties.

L’extrait de code C# suivant indique comment créer une tâche d’exportation :

// Call an export job on the IoT hub to retrieve all devices
JobProperties exportJob = await 
  registryManager.ExportDevicesAsync(containerSasUri, false);

Notes

Pour utiliser la classe RegistryManager dans votre code C#, ajoutez le package NuGet Microsoft.Azure.Devices à votre projet. La classe RegistryManager se trouve dans l’espace de noms Microsoft.Azure.Devices.

Vous pouvez utiliser la classe RegistryManager pour interroger l’état de la tâche à l’aide des métadonnées JobProperties retournées. Pour créer une instance de la classe RegistryManager, utilisez la méthode CreateFromConnectionString.

RegistryManager registryManager =
  RegistryManager.CreateFromConnectionString("{your IoT Hub connection string}");

Pour obtenir la chaîne de connexion de votre IoT Hub dans le portail Azure :

  1. Accédez à votre hub IoT.

  2. Sélectionnez Stratégies d’accès partagé.

  3. Sélectionnez une stratégie en tenant compte des autorisations requises.

  4. Copiez la chaîne de connexion pour cette stratégie.

L'extrait de code C# suivant, tiré de la méthode WaitForJobAsync de l'exemple de SDK, montre comment interroger toutes les cinq secondes pour savoir si le travail a fini de s'exécuter :

// Wait until job is finished
while (true)
{
    job = await registryManager.GetJobAsync(job.JobId);
    if (job.Status == JobStatus.Completed
        || job.Status == JobStatus.Failed
        || job.Status == JobStatus.Cancelled)
    {
        // Job has finished executing
        break;
    }
    Console.WriteLine($"\tJob status is {job.Status}...");

    await Task.Delay(TimeSpan.FromSeconds(5));
}

Notes

Si votre compte de stockage a des configurations de pare-feu qui limitent la connectivité d’IoT Hub, envisagez d’utiliser l’exception interne approuvée de Microsoft (disponible dans certaines régions pour les hubs IoT avec identité de service managée).

Limites de travail d’importation/exportation d’appareil

Une seule tâche active d'importation ou d'exportation d'appareils est autorisée à la fois pour tous les niveaux d'IoT Hub. IoT Hub dispose également de limites concernant le taux des opérations de travaux. Pour plus d’informations, consultez Quotas et limitation d’IoT Hub.

Exporter des appareils

Utilisez la méthode ExportDevicesAsync pour exporter l’intégralité d’un registre des identités IoT Hub vers un conteneur Blob Stockage Azure à l’aide d’une signature d’accès partagé (SAS). Cette méthode vous permet de créer des sauvegardes fiables de vos informations d’appareil dans un conteneur d’objets blob que vous contrôlez.

La méthode ExportDevicesAsync requiert deux paramètres :

  • Une chaîne qui contient l’URI d’un conteneur d’objets blob. Cet URI doit contenir un jeton SAP, qui accorde l’accès en écriture au conteneur. La tâche crée un objet blob de blocs dans ce conteneur pour stocker les données d’exportation d’appareil sérialisées. Le jeton SAP doit inclure ces autorisations :

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    
  • Un booléen qui indique si vous souhaitez exclure les clés d’authentification de vos données d’exportation. Si la valeur est false, des clés d’authentification sont incluses dans la sortie d’exportation. Dans le cas contraire, les clés sont exportées sous forme de valeur null.

L’extrait de code C# suivant montre comment lancer une tâche d’exportation qui inclut des clés d’authentification de l’appareil dans les données d’exportation, puis comment interroger l’exécution :

// Call an export job on the IoT Hub to retrieve all devices
JobProperties exportJob = 
  await registryManager.ExportDevicesAsync(containerSasUri, false);

// Wait until job is finished
while(true)
{
    exportJob = await registryManager.GetJobAsync(exportJob.JobId);
    if (exportJob.Status == JobStatus.Completed || 
        exportJob.Status == JobStatus.Failed ||
        exportJob.Status == JobStatus.Cancelled)
    {
    // Job has finished executing
    break;
    }

    await Task.Delay(TimeSpan.FromSeconds(5));
}

Vous trouverez du code similaire dans la méthode ExportDevicesAsync de l’exemple sdk. La tâche stocke sa sortie dans le conteneur d’objets blob fourni en tant qu’objet blob de blocs portant le nom devices.txt. Les données de sortie consistent en des données d’appareil JSON sérialisées, avec un appareil par ligne.

L’exemple ci-après illustre les données de sortie :

{"id":"Device1","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device2","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device3","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device4","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device5","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}

Si un appareil comporte des données de représentation, elles sont également exportées avec les données de l’appareil. L’exemple ci-après illustre ce format. Toutes les données à partir de la ligne « twinETag » jusqu'à la fin sont des données de représentation.

{
   "id":"export-6d84f075-0",
   "eTag":"MQ==",
   "status":"enabled",
   "authentication":null,
   "twinETag":"AAAAAAAAAAI=",
   "tags":{
      "Location":"LivingRoom"
   },
   "properties":{
      "desired":{
         "Thermostat":{
            "Temperature":75.1,
            "Unit":"F"
         },
      },
      "reported":{}
   }
}

Si vous avez besoin d'accéder à ces données sous forme de code, vous pouvez désérialiser ces données à l'aide de la classe ExportImportDevice. L'extrait de code C# suivant, tiré de la méthode ReadFromBlobAsync de l'exemple SDK, montre comment lire dans une instance BlobClient les informations sur le périphérique qui ont été précédemment exportées à partir de ExportImportDevice :

private static async Task<List<string>> ReadFromBlobAsync(BlobClient blobClient)
{
    // Read the blob file of devices, import each row into a list.
    var contents = new List<string>();

    using Stream blobStream = await blobClient.OpenReadAsync();
    using var streamReader = new StreamReader(blobStream, Encoding.UTF8);
    while (streamReader.Peek() != -1)
    {
        string line = await streamReader.ReadLineAsync();
        contents.Add(line);
    }

    return contents;
}

Importer des appareils

La méthode ImportDevicesAsync de la classe RegistryManager vous permet d’effectuer des opérations d’importation et de synchronisation en bloc dans un registre des identités IoT Hub. À la manière de la méthode ExportDevicesAsync, la méthode ImportDevicesAsync utilise l’infrastructure de Tâches.

Faites attention lors de l’utilisation de la méthode ImportDevicesAsync. Celle-ci peut, en plus d’approvisionner de nouveaux appareils dans le registre des identités, mettre à jour et supprimer des appareils existants.

Avertissement

Il est impossible d’annuler une opération d’importation. Sauvegardez toujours vos données existantes vers un autre conteneur d’objets blob, à l’aide de la méthode ExportDevicesAsync, avant de faire des modifications en bloc dans le registre des identités.

La méthode ImportDevicesAsync requiert deux paramètres :

  • Une chaîne qui contient l'URI d'un conteneur d'objets blob stockage Azure à utiliser comme entrée de la tâche. Cet URI doit contenir un jeton SAP qui accorde l’accès en lecture au conteneur. Ce conteneur doit inclure un objet blob du nom de devices.txt contenant les données d’appareil sérialisées pour importation dans le registre des identités. Les données d’importation doivent contenir des informations sur l’appareil au même format JSON que celui utilisé par la tâche ExportImportDevice lors de la création d’un objet blob devices.txt. Le jeton SAP doit inclure ces autorisations :

    SharedAccessBlobPermissions.Read
    
  • Une chaîne qui contient l'URI d'un conteneur d'objets blob stockage Azure à utiliser comme sortie de la tâche. La tâche crée un objet blob de blocs dans ce conteneur pour stocker toute information d'erreur à partir de la tâche d'importation terminée. Le jeton SAP doit inclure ces autorisations :

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    

Notes

Les deux paramètres peuvent pointer vers le même conteneur d’objets blob. Les paramètres distincts permettent simplement davantage de contrôle sur vos données, étant donné que le conteneur de sortie requiert des autorisations supplémentaires.

L’extrait de code C# suivant indique comment créer une tâche d’importation :

JobProperties importJob = 
   await registryManager.ImportDevicesAsync(containerSasUri, containerSasUri);

Cette méthode peut également être utilisée pour importer les données pour la représentation de l’appareil. Le format des données d’entrée est le même que celui indiqué dans la section ExportDevicesAsync. De cette façon, vous pouvez réimporter les données exportées.

Comportement d’importation

Vous pouvez utiliser la méthode ImportDevicesAsync pour effectuer les opérations en bloc suivantes dans le registre des identités :

  • Inscription en bloc de nouveaux appareils
  • Suppression en bloc d’appareils existants
  • Modifications d’état en bloc (activer ou désactiver des appareils)
  • Attribution en bloc de nouvelles clés d’authentification d’appareils
  • Régénération automatique en bloc des clés d'authentification des appareils
  • Mise à jour en bloc de données de représentation

Vous pouvez effectuer n’importe quelle combinaison des opérations précédentes en un seul appel ImportDevicesAsync . Vous pouvez, par exemple, inscrire de nouveaux appareils, tout en supprimant ou mettant à jour des appareils existants. Lorsqu’il est utilisé avec la méthode ExportDevicesAsync , tous les appareils peuvent être migrés complètement à partir d’un IoT Hub vers un autre.

Vous pouvez contrôler le processus d’importation par appareil en utilisant la propriété facultative importMode dans les données d’importation sérialisées pour chaque appareil. La propriété importMode propose les options suivantes :

  • Créer
  • CreateOrUpdate (par défaut)
  • CreateOrUpdateIfMatchETag
  • Supprimer
  • DeleteIfMatchETag
  • Update
  • UpdateIfMatchETag
  • UpdateTwin
  • UpdateTwinIfMatchETag

Pour plus d'informations sur chacune de ces options de mode d'importation, consultez ImportMode

Résoudre des problèmes liés aux tâches d'importation

L'utilisation d'une tâche d'importation pour créer des appareils peut échouer avec un problème de quota à l'approche de la limite de nombre d'appareils du hub IoT. Cette défaillance peut se produire même si le nombre total de périphériques est encore inférieur à la limite du quota. L’erreur IotHubQuotaExceeded (403002) est retournée avec le message d’erreur suivant : « Le nombre total d’appareils sur le hub Iot dépasse le quota alloué ».

Si cette erreur s’affiche, vous pouvez utiliser la requête suivante pour renvoyer le nombre total d’appareils inscrits sur votre hub IoT :

SELECT COUNT() as totalNumberOfDevices FROM devices

Pour plus d’informations sur le nombre total d’appareils pouvant être inscrits sur un hub IoT, consultez Limites de hub IoT (IoT Hub).

S’il existe toujours un quota disponible, vous pouvez examiner le blob de sortie de travail pour les appareils ayant échoué avec l’erreur IotHubQuotaExceeded (403002). Vous pouvez ensuite essayer d’ajouter ces appareils individuellement au hub IoT. Par exemple, vous pouvez utiliser les méthodes AddDeviceAsync ou AddDeviceWithTwinAsync. N'essayez pas d'ajouter les appareils à l'aide d'une autre tâche, car vous risquez de rencontrer la même erreur.

Exemple d’importation d’appareils : approvisionnement d’appareils en bloc

L’extrait de code C# suivant, issu de la méthode GenerateDevicesAsync dans l’exemple sdk, montre comment générer plusieurs identités d’appareil qui :

  • Comprennent des clés d’authentification.
  • Écrivent ces informations d’appareil sur un objet blob de blocs.
  • Importent les appareils dans le registre des identités.
private async Task GenerateDevicesAsync(RegistryManager registryManager, int numToAdd)
{
    var stopwatch = Stopwatch.StartNew();

    Console.WriteLine($"Creating {numToAdd} devices for the source IoT hub.");
    int interimProgressCount = 0;
    int displayProgressCount = 1000;
    int totalProgressCount = 0;

    // generate reference for list of new devices we're going to add, will write list to this blob
    BlobClient generateDevicesBlob = _blobContainerClient.GetBlobClient(_generateDevicesBlobName);

    // define serializedDevices as a generic list<string>
    var serializedDevices = new List<string>(numToAdd);

    for (int i = 1; i <= numToAdd; i++)
    {
        // Create device name with this format: Hub_00000000 + a new guid.
        // This should be large enough to display the largest number (1 million).
        string deviceName = $"Hub_{i:D8}_{Guid.NewGuid()}";
        Debug.Print($"Adding device '{deviceName}'");

        // Create a new ExportImportDevice.
        var deviceToAdd = new ExportImportDevice
        {
            Id = deviceName,
            Status = DeviceStatus.Enabled,
            Authentication = new AuthenticationMechanism
            {
                SymmetricKey = new SymmetricKey
                {
                    PrimaryKey = GenerateKey(32),
                    SecondaryKey = GenerateKey(32),
                }
            },
            // This indicates that the entry should be added as a new device.
            ImportMode = ImportMode.Create,
        };

        // Add device to the list as a serialized object.
        serializedDevices.Add(JsonConvert.SerializeObject(deviceToAdd));

        // Not real progress as you write the new devices, but will at least show *some* progress.
        interimProgressCount++;
        totalProgressCount++;
        if (interimProgressCount >= displayProgressCount)
        {
            Console.WriteLine($"Added {totalProgressCount}/{numToAdd} devices.");
            interimProgressCount = 0;
        }
    }

    // Now have a list of devices to be added, each one has been serialized.
    // Write the list to the blob.
    var sb = new StringBuilder();
    serializedDevices.ForEach(serializedDevice => sb.AppendLine(serializedDevice));

    // Write list of serialized objects to the blob.
    using Stream stream = await generateDevicesBlob.OpenWriteAsync(overwrite: true);
    byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
    for (int i = 0; i < bytes.Length; i += BlobWriteBytes)
    {
        int length = Math.Min(bytes.Length - i, BlobWriteBytes);
        await stream.WriteAsync(bytes.AsMemory(i, length));
    }
    await stream.FlushAsync();

    Console.WriteLine("Running a registry manager job to add the devices.");

    // Should now have a file with all the new devices in it as serialized objects in blob storage.
    // generatedListBlob has the list of devices to be added as serialized objects.
    // Call import using the blob to add the new devices.
    // Log information related to the job is written to the same container.
    // This normally takes 1 minute per 100 devices (according to the docs).

    // First, initiate an import job.
    // This reads in the rows from the text file and writes them to IoT Devices.
    // If you want to add devices from a file, you can create a file and use this to import it.
    //   They have to be in the exact right format.
    try
    {
        // The first URI is the container to import from; the file defaults to devices.txt, but may be specified.
        // The second URI points to the container to write errors to as a blob.
        // This lets you import the devices from any file name. Since we wrote the new
        // devices to [devicesToAdd], need to read the list from there as well.
        var importGeneratedDevicesJob = JobProperties.CreateForImportJob(
            _containerUri,
            _containerUri,
            _generateDevicesBlobName);
        importGeneratedDevicesJob = await registryManager.ImportDevicesAsync(importGeneratedDevicesJob);
        await WaitForJobAsync(registryManager, importGeneratedDevicesJob);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Adding devices failed due to {ex.Message}");
    }

    stopwatch.Stop();
    Console.WriteLine($"GenerateDevices, time elapsed = {stopwatch.Elapsed}.");
}

Exemple d’importation d’appareils : suppression en bloc

L'extrait de code C# suivant, tiré de la méthode DeleteFromHubAsync de l'exemple SDK, montre comment supprimer tous les appareils d'un concentrateur IoT :

private async Task DeleteFromHubAsync(RegistryManager registryManager, bool includeConfigurations)
{
    var stopwatch = Stopwatch.StartNew();

    Console.WriteLine("Deleting all devices from an IoT hub.");

    Console.WriteLine("Exporting a list of devices from IoT hub to blob storage.");

    // Read from storage, which contains serialized objects.
    // Write each line to the serializedDevices list.
    BlobClient devicesBlobClient = _blobContainerClient.GetBlobClient(_destHubDevicesImportBlobName);

    Console.WriteLine("Reading the list of devices in from blob storage.");
    List<string> serializedDevices = await ReadFromBlobAsync(devicesBlobClient);

    // Step 1: Update each device's ImportMode to be Delete
    Console.WriteLine("Updating ImportMode to be 'Delete' for each device and writing back to the blob.");
    var sb = new StringBuilder();
    serializedDevices.ForEach(serializedEntity =>
    {
        // Deserialize back to an ExportImportDevice and change import mode.
        ExportImportDevice device = JsonConvert.DeserializeObject<ExportImportDevice>(serializedEntity);
        device.ImportMode = ImportMode.Delete;

        // Reserialize the object now that we've updated the property.
        sb.AppendLine(JsonConvert.SerializeObject(device));
    });

    // Step 2: Write the list in memory to the blob.
    BlobClient deleteDevicesBlobClient = _blobContainerClient.GetBlobClient(_hubDevicesCleanupBlobName);
    await WriteToBlobAsync(deleteDevicesBlobClient, sb.ToString());

    // Step 3: Call import using the same blob to delete all devices.
    Console.WriteLine("Running a registry manager job to delete the devices from the IoT hub.");
    var importJob = JobProperties.CreateForImportJob(
        _containerUri,
        _containerUri,
        _hubDevicesCleanupBlobName);
    importJob = await registryManager.ImportDevicesAsync(importJob);
    await WaitForJobAsync(registryManager, importJob);

    // Step 4: delete configurations
    if (includeConfigurations)
    {
        BlobClient configsBlobClient = _blobContainerClient.GetBlobClient(_srcHubConfigsExportBlobName);
        List<string> serializedConfigs = await ReadFromBlobAsync(configsBlobClient);
        foreach (string serializedConfig in serializedConfigs)
        {
            try
            {
                Configuration config = JsonConvert.DeserializeObject<Configuration>(serializedConfig);
                await registryManager.RemoveConfigurationAsync(config.Id);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to deserialize or remove a config.\n\t{serializedConfig}\n\n{ex.Message}");
            }
        }
    }

    stopwatch.Stop();
    Console.WriteLine($"Deleted IoT hub devices and configs: time elapsed = {stopwatch.Elapsed}");
}

Obtenir l’URI SAS du conteneur

L’exemple de code suivant montre comment générer un URI de SAP avec des autorisations de lecture, d’écriture et de suppression pour un conteneur d’objets blob :

static string GetContainerSasUri(CloudBlobContainer container)
{
  // Set the expiry time and permissions for the container.
  // In this case no start time is specified, so the
  // shared access signature becomes valid immediately.
  var sasConstraints = new SharedAccessBlobPolicy();
  sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24);
  sasConstraints.Permissions = 
    SharedAccessBlobPermissions.Write | 
    SharedAccessBlobPermissions.Read | 
    SharedAccessBlobPermissions.Delete;

  // Generate the shared access signature on the container,
  // setting the constraints directly on the signature.
  string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);

  // Return the URI string for the container,
  // including the SAS token.
  return container.Uri + sasContainerToken;
}

Étapes suivantes

Dans cet article, vous avez appris comment effectuer des opérations en bloc dans le registre des identités dans un IoT Hub. La plupart de ces opérations, y compris la manière de déplacer des appareils d'un hub à un autre, sont utilisées dans la section Gérer les appareils enregistrés sur le hub IoT de Comment migrer manuellement un hub Azure IoT à l'aide d'un modèle Azure Resource Manager.