Importare ed esportare in blocco le identità dei dispositivi dell'hub IoT

Ogni hub IoT ha un registro delle identità che è possibile usare per creare risorse del dispositivo nel servizio. e per consentire di controllare gli accessi agli endpoint per il dispositivo. Questo articolo descrive come importare ed esportare le identità dei dispositivi in blocco da e verso un registro delle identità usando l'esempio ImportExportDeviceSample incluso in Microsoft Azure IoT SDK per .NET. Per altre informazioni su come usare questa funzionalità durante la migrazione di un hub IoT a un'area diversa, vedere Come eseguire manualmente la migrazione di un hub IoT di Azure usando un modello di Azure Resource Manager.

Nota

hub IoT recentemente aggiunto il supporto della rete virtuale in un numero limitato di aree. Questa funzionalità protegge le operazioni di importazione ed esportazione ed elimina la necessità di passare le chiavi per l'autenticazione. Attualmente, il supporto della rete virtuale è disponibile solo in queste aree: WestUS2, EastUS e SouthCentralUS. Per altre informazioni sul supporto della rete virtuale e sulle chiamate API per implementarlo, vedere hub IoT Supporto per le reti virtuali.

Le operazioni di importazione ed esportazione vengono eseguite nel contesto dei processi che consentono di eseguire operazioni di servizio bulk su un hub IoT.

La classe RegistryManager nell'SDK include i metodi ExportDevicesAsync e ImportDevicesAsync che usano il framework job . Questi metodi consentono di esportare, importare e sincronizzare un intero registro delle identità dell'hub IoT.

Questo articolo illustra l'uso della classe RegistryManager e del sistema job per eseguire importazioni ed esportazioni bulk di dispositivi da e verso il registro delle identità di un hub IoT. È anche possibile usare il servizio Device Provisioning hub IoT di Azure per abilitare il provisioning JIT senza tocco in uno o più hub IoT. Per altre informazioni, vedere la documentazione di servizio per il provisioning.

Nota

Alcuni frammenti di codice in questo articolo sono inclusi nell'esempio di servizio ImportExportDevicesSample fornito con Microsoft Azure IoT SDK per .NET. L'esempio si trova nella /iothub/service/samples/how to guides/ImportExportDevicesSample cartella dell'SDK e, dove specificato, i frammenti di codice vengono inclusi dal file per l'esempio ImportExportDevicesSample.cs sdk. Per altre informazioni sull'esempio ImportExportDevicesSample e altri esempi di servizio inclusi nella for.NET azure IoT SDK, vedere Esempi di servizio dell'hub IoT di Azure per C#.

Informazioni sui processi

Le operazioni del Registro di sistema delle identità usano il sistema di processi quando l'operazione:

  • Ha un tempo di esecuzione potenzialmente lungo rispetto alle operazioni di runtime standard.

  • Restituisce all'utente una grande quantità di dati.

Anziché una singola chiamata API in attesa o blocco sul risultato dell'operazione, l'operazione crea in modo asincrono un processo per l'hub IoT. quindi restituisce immediatamente un oggetto JobProperties.

Il frammento di codice C# seguente mostra come creare un processo di esportazione:

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

Nota

Per usare la classe il RegistryManager nel codice c#, aggiungere il pacchetto NuGet Microsoft.Azure.Devices al progetto. La classe RegistryManager si trova nello spazio dei nomi Microsoft.Azure.Devices.

È possibile usare la classe RegistryManager per eseguire query sullo stato del processo usando i metadati di JobProperties restituiti. Per creare un'istanza della classe RegistryManager, usare il metodo CreateFromConnectionString.

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

Per trovare la stringa di connessione dell'hub IoT, seguire questa procedura nel portale di Azure:

  1. Passare all'hub IoT.

  2. Selezionare Criteri di accesso condiviso.

  3. Selezionare un criterio prendendo in considerazione le autorizzazioni necessarie.

  4. Copiare il stringa di connessione per tale criterio.

Il frammento di codice C# seguente, dal metodo WaitForJobAsync nell'esempio SDK, mostra come eseguire il polling ogni cinque secondi per verificare se il processo è terminato l'esecuzione:

// 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));
}

Nota

Se l'account di archiviazione dispone di configurazioni del firewall che limitano la connettività di hub IoT, provare a usare l'eccezione di prima parte attendibile di Microsoft (disponibile nelle aree selezionate per gli hub IoT con identità del servizio gestito).

Limiti dei processi di importazione/esportazione dei dispositivi

Per tutti i livelli di hub IoT è consentito un solo processo di importazione o esportazione del dispositivo attivo alla volta. hub IoT ha anche limiti per la frequenza delle operazioni di processi. Per altre informazioni, vedere hub IoT quote e limitazioni.

Esportare dispositivi

Usare il metodo ExportDevicesAsync per esportare l'intero registro delle identità di un hub IoT in un contenitore BLOB Archiviazione di Azure usando una firma di accesso condiviso.Use the ExportDevicesAsync method to export the entirety of an IoT hub identity registry to an IoT hub identity registry to an Archiviazione di Azure blob container using a shared access signature (SAS). Questo metodo consente di creare backup affidabili delle informazioni sui dispositivi in un contenitore BLOB che si controlla.

Il metodo ExportDevicesAsync richiede due parametri:

  • Una stringa che contiene un URI di un contenitore BLOB. Questo URI deve contenere un token di firma di accesso condiviso che concede l'accesso in scrittura al contenitore. Il processo crea un BLOB in blocchi in questo contenitore per archiviare i dati di esportazione del dispositivo serializzati. Il token di firma di accesso condiviso deve includere queste autorizzazioni:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    
  • Oggetto booleano che indica se si vogliono escludere le chiavi di autenticazione dai dati di esportazione. Se il valore è false, le chiavi di autenticazione sono incluse nell'output di esportazione. In caso contrario, le chiavi vengono esportate come null.

I frammenti di codice C# seguenti illustrano come inizializzare un processo di esportazione che include chiavi di autenticazione di dispositivi nei dati di esportazione e quindi eseguire il polling del completamento:

// 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));
}

È possibile trovare codice simile nel metodo ExportDevicesAsync dell'esempio SDK. Il processo archivia l'output nel contenitore BLOB specificato come BLOB in blocchi con il nome devices.txt. I dati di output sono costituiti da dati del dispositivo serializzati in formato JSON, con un dispositivo per ogni riga.

Nell'esempio seguente vengono descritti i dati di output:

{"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="}}}

Se un dispositivo contiene dati gemelli, allora anche i dati gemelli vengono esportati con i dati del dispositivo. Questo formato è illustrato nell'esempio seguente. Tutti i dati dalla riga di "twinETag" fino alla fine sono dati gemelli.

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

Se è necessario accedere a questi dati nel codice, è possibile deserializzare questi dati usando la classe ExportImportDevice . Il frammento di codice C# seguente, dal metodo ReadFromBlobAsync nell'esempio SDK, illustra come leggere le informazioni sul dispositivo esportate in precedenza da ExportImportDevice in un'istanza BlobClient :

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;
}

Importare dispositivi

Il metodo ImportDevicesAsync nella classe RegistryManager consente di eseguire operazioni di importazione e sincronizzazione in blocco nel registro delle identità di un hub IoT. In modo analogo al metodo ExportDevicesAsync, il metodo ImportDevicesAsync usa il framework Job.

Prestare attenzione quando si usa il metodo ImportDevicesAsync in quanto, oltre a eseguire il provisioning dei dispositivi nuovi nel registro delle identità, può anche aggiornare ed eliminare dispositivi esistenti.

Avviso

Un'operazione di importazione non può essere annullata. Eseguire sempre il backup dei dati esistenti usando il metodo ExportDevicesAsync in un altro contenitore BLOB, prima di apportare modifiche in blocco al registro delle identità.

Il metodo ImportDevicesAsync usa due parametri:

  • Stringa che contiene un URI di un contenitore BLOB Archiviazione di Azure da usare come input per il processo. Questo URI deve contenere un token di firma di accesso condiviso che concede l'accesso in lettura al contenitore. Questo contenitore deve includere un BLOB con il nome devices.txt che contiene i dati serializzati del dispositivo da importare nel registro delle identità. I dati di importazione devono contenere informazioni sul dispositivo nello stesso formato JSON usato dal processo ExportImportDevice quando viene creato il BLOB devices.txt. Il token di firma di accesso condiviso deve includere queste autorizzazioni:

    SharedAccessBlobPermissions.Read
    
  • Stringa che contiene un URI di un contenitore BLOB Archiviazione di Azure da usare come output del processo. Il processo crea un BLOB in blocchi in questo contenitore per archiviare eventuali informazioni sugli errori dal processo di importazione completato. Il token di firma di accesso condiviso deve includere queste autorizzazioni:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    

Nota

I due parametri possono puntare allo stesso contenitore BLOB. I parametri separati consentono semplicemente un maggiore controllo dei dati, perché il contenitore di output richiede autorizzazioni aggiuntive.

Il frammento di codice C# seguente mostra come avviare un processo di importazione:

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

Questo metodo può essere usato anche per importare i dati per il dispositivo gemello. Il formato per i dati di input è uguale al formato visualizzato nella sezione ExportDevicesAsync. In questo modo, è possibile reimportare i dati esportati.

Importare il comportamento

È possibile usare il metodo ImportDevicesAsync per eseguire le operazioni in blocco seguenti nel registro delle identità:

  • Registrazione in blocco di nuovi dispositivi
  • Eliminazioni in blocco dei dispositivi esistenti
  • Modifiche dello stato in blocco (abilitare o disabilitare dispositivi)
  • Assegnazione in blocco di nuove chiavi di autenticazione del dispositivo
  • Rigenerazione automatica bulk delle chiavi di autenticazione del dispositivo
  • Aggiornamento bulk dei dati gemelli

È possibile eseguire una combinazione qualsiasi delle operazioni precedenti in un'unica chiamata ImportDevicesAsync . Ad esempio, è possibile registrare nuovi dispositivi ed eliminare o aggiornare contemporaneamente quelli esistenti. Insieme con il metodo ExportDevicesAsync , è possibile eseguire la migrazione completa di tutti i dispositivi da un hub IoT all'altro.

Usare la proprietà facoltativa importMode nei dati di serializzazione dell'importazione per ogni dispositivo per controllare il processo di importazione per dispositivo. La proprietà importMode include le opzioni seguenti:

  • Crea
  • CreateOrUpdate (impostazione predefinita)
  • CreateOrUpdateIfMatchETag
  • CANC
  • DeleteIfMatchETag
  • Aggiornamento
  • UpdateIfMatchETag
  • UpdateTwin
  • UpdateTwinIfMatchETag

Per informazioni dettagliate su ognuna di queste opzioni della modalità di importazione, vedere ImportMode

Risolvere i problemi relativi ai processi di importazione

L'uso di un processo di importazione per creare dispositivi potrebbe non riuscire con un problema di quota quando è vicino al limite del numero di dispositivi dell'hub IoT. Questo errore può verificarsi anche se il numero totale di dispositivi è ancora inferiore al limite di quota. L'errore IotHubQuotaExceeded (403002) viene restituito con il messaggio di errore seguente: "Numero totale di dispositivi in IotHub ha superato la quota allocata".

Se viene visualizzato questo errore, è possibile usare la query seguente per restituire il numero totale di dispositivi registrati nell'hub IoT:

SELECT COUNT() as totalNumberOfDevices FROM devices

Per informazioni sul numero totale di dispositivi che possono essere registrati in un hub IoT, vedere limiti hub IoT.

Se è ancora disponibile una quota, è possibile esaminare il BLOB di output del processo per i dispositivi che hanno avuto esito negativo con l'errore IotHubQuotaExceeded (403002). È quindi possibile provare ad aggiungere questi dispositivi singolarmente all'hub IoT. Ad esempio, è possibile usare i metodi AddDeviceAsync o AddDeviceWithTwinAsync . Non provare ad aggiungere i dispositivi usando un altro processo perché potrebbe verificarsi lo stesso errore.

Importare dispositivi: esempio di provisioning dei dispositivi in blocco

Il frammento di codice C# seguente, dal metodo GenerateDevicesAsync nell'esempio SDK, illustra come generare più identità del dispositivo che:

  • Includono chiavi di autenticazione.
  • Scrivono le informazioni del dispositivo in un BLOB in blocchi.
  • Importano i dispositivi nel registro delle identità.
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}.");
}

Importare dispositivi: esempio di eliminazione in blocco

Il frammento di codice C# seguente, dal metodo DeleteFromHubAsync nell'esempio SDK, illustra come eliminare tutti i dispositivi da un hub 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}");
}

Recuperare l'URI di firma di accesso condiviso del contenitore

Il codice di esempio seguente illustra come generare un URI di firma di accesso condiviso con autorizzazioni di lettura, scrittura ed eliminazione per un contenitore 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;
}

Passaggi successivi

In questo articolo si è appreso come eseguire operazioni in blocco sul registro delle identità in un hub IoT. Molte di queste operazioni, tra cui come spostare i dispositivi da un hub a un altro, vengono usate nella sezione Gestire i dispositivi registrati nell'hub IoT di Come eseguire manualmente la migrazione di un hub IoT di Azure usando un modello di Azure Resource Manager.