Importar e exportar em massa identidades de dispositivos do Hub IoT

Cada Hub IoT tem um registro de identidade que você pode usar para criar recursos de dispositivos no serviço. O registro de identidade também permite que você controle o acesso aos pontos de extremidade voltados para o dispositivo. Este artigo descreve como importar e exportar identidades de dispositivo em massa de e para um registro de identidade, usando o exemplo ImportExportDeviceSample incluído no SDK do IoT do Microsoft Azure para .NET. Para mais informações sobre como usar esse recurso ao migrar um hub IoT para uma região diferente, consulte Como migrar um Hub IoT do Azure usando os modelos do Azure Resource Manager.

Observação

Recentemente, o Hub IoT adicionou suporte a redes virtuais em um número limitado de regiões. Esse recurso garante operações de importação e exportação e elimina a necessidade de passar chaves para autenticação. No momento, o suporte a redes virtuais está disponível apenas nessas regiões: WestUS2, EastUS e SouthCentralUS. Saiba mais sobre o suporte à rede virtual e as chamadas à API para implementá-lo em Suporte do Hub IoT para redes virtuais.

As operações de importação e exportação ocorrem no contexto de trabalhos, que permitem aos usuários executar operações de serviço em massa em um Hub IoT.

A classe RegistryManager no SDK inclui os métodos ExportDevicesAsync e ImportDevicesAsync que usam a estrutura Job. Esses métodos permitem exportar, importar e sincronizar todo o registro de identidade de um Hub IoT.

Este artigo discute o uso da classe RegistryManager e do sistema de Trabalho para executar importações e exportações em massa de dispositivos do e para o registro de identidade de um hub IoT. Você também pode usar o Serviço de Provisionamento de Dispositivos no Hub IoT do Azure para habilitar o provisionamento zero-touch e just-in-time para um ou mais hubs IoT. Para obter mais informações, consulte a documentação do serviço de provisionamento.

Observação

Alguns dos snippets de código neste artigo estão incluídos no exemplo de serviço ImportExportDevicesSample fornecido com o SDK do IoT do Microsoft Azure para .NET. O exemplo está localizado na pasta /iothub/service/samples/how to guides/ImportExportDevicesSample do SDK e, onde especificado, os snippets de código são incluídos do arquivo ImportExportDevicesSample.cs para esse exemplo do SDK. Para obter mais informações sobre o exemplo ImportExportDevicesSample e outros exemplos de serviço incluídos no SDK do IoT do Azure para .NET, consulte Exemplos de serviço do Hub IoT do Azure para C#.

O que são trabalhos?

As operações de registro de identidade usam o sistema de trabalhos quando a operação:

  • Tem um tempo de execução potencialmente longo em comparação com as operações de tempo de execução padrão.

  • Retorna uma grande quantidade de dados para o usuário.

Em vez de ter uma única chamada à API aguardando ou bloqueando o resultado da operação, a operação cria de forma assíncrona um trabalho para aquele hub IoT. Então, a operação retorna imediatamente um objeto JobProperties.

O snippet de código de C# a seguir mostra como criar um trabalho de exportação:

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

Observação

Para usar a classe RegistryManager no código C#, adicione o pacote NuGet Microsoft.Azure.Devices ao projeto. A classe RegistryManager está no namespace Microsoft.Azure.Devices.

Você pode usar a classe RegistryManager para consultar o estado do Job usando os metadados de JobProperties retornados. Para criar uma instância da classe RegistryManager, use o método CreateFromConnectionString.

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

Para encontrar a cadeia de conexão do para seu Hub IoT no Portal do Azure:

  1. Navegue até o seu Hub IoT.

  2. Selecione Políticas de acesso compartilhado.

  3. Selecione uma política, levando em conta as permissões necessárias.

  4. Copie a cadeia de conexão para essa política.

O snippet de código de C# a seguir, do método WaitForJobAsync no exemplo do SDK, mostra como verificar a cada cinco segundos para ver se o trabalho finalizou a execução:

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

Observação

Se sua conta de armazenamento tiver configurações de firewall que restringem a conectividade do Hub IoT, considere usar a exceção de primeira parte confiável da Microsoft (disponível em regiões selecionadas para hubs IoT com identidade do serviço gerenciado).

Limites de trabalho de importação/exportação de dispositivos

Só é permitido um trabalho ativo de importação ou exportação de dispositivos por vez para todas as camadas do Hub IoT. O Hub IoT também tem limites para a taxa de operações de trabalhos. Saiba mais em Limitação da largura de banda e cotas do Hub IoT.

Exportar dispositivos

Use o método ExportDevicesAsync para exportar todo o registro de identidade de um Hub IoT para um contêiner de blobs do Armazenamento do Microsoft Azure usando uma SAS (Assinatura de Acesso Compartilhado). Esse método permite criar backups confiáveis das informações do dispositivo em um contêiner de blobs controlado por você.

O método ExportDevicesAsync exige dois parâmetros:

  • Uma cadeia de caracteres que contenha um URI de um contêiner de blobs. Esse URI deve conter um token SAS que conceda acesso de gravação ao contêiner. O trabalho cria um blob de blocos nesse contêiner para armazenar os dados do dispositivo de exportação serializados. O token SAS deve incluir estas permissões:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    
  • Um booliano que indica se você deseja excluir chaves de autenticação dos dados de exportação. Se for false, as chaves de autenticação serão incluídas na saída de exportação. Caso contrário, as chaves serão exportadas como null.

O seguinte snippet de código em C# mostra como iniciar um trabalho de exportação que inclui chaves de autenticação de dispositivo nos dados de exportação e, em seguida, pesquisar se houve a conclusão:

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

Você pode encontrar um código semelhante no método ExportDevicesAsync no exemplo do SDK. O trabalho armazena sua saída no contêiner de blobs fornecido como um blob de blocos com o nome devices.txt. Os dados da saída consistem em dados de dispositivo serializados JSON, com um dispositivo por linha.

O exemplo a seguir mostra os dados de saída:

{"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 um dispositivo tiver dados gêmeos, os dados gêmeos também serão exportados junto com os dados do dispositivo. O exemplo a seguir mostra esse formato. Todos os dados da linha "twinETag" até o final são dados gêmeos.

{
   "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 precisar acessar esses dados em código, você poderá desserializá-los usando a classe ExportImportDevice. O snippet de código C# a seguir, do método ReadFromBlobAsync no exemplo do SDK, mostra como ler as informações do dispositivo exportadas anteriormente de ExportImportDevice para uma instância do 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;
}

Importar dispositivos

O método ImportDevicesAsync na classe RegistryManager permite que você execute operações em massa de importação e sincronização em um registro de identidade do Hub IoT. Assim como ocorre com o método ExportDevicesAsync, o método ImportDevicesAsync usa a estrutura Job.

Tenha cuidado ao usar o método ImportDevicesAsync porque, além de provisionar novos dispositivos no registro de identidade, ele também pode atualizar e excluir os dispositivos existentes.

Aviso

Uma operação de importação não pode ser desfeita. Sempre faça backup de seus dados existentes usando o método ExportDevicesAsync em outro contêiner de blobs antes de fazer alterações em massa no registro de identidade.

O método ImportDevicesAsync usa dois parâmetros:

  • Uma cadeia de caracteres que contém um URI de um contêiner de blobs do Armazenamento do Microsoft Azure como entrada para o trabalho. Esse URI deve conter um token SAS que conceda acesso de leitura ao contêiner. Esse contêiner deve conter um blob com o nome devices.txt que contém os dados de dispositivo serializados a serem importados no registro de identidade. Os dados de importação devem conter informações do dispositivo no mesmo formato JSON usado pelo trabalho ExportImportDevice ao criar um blob devices.txt. O token SAS deve incluir estas permissões:

    SharedAccessBlobPermissions.Read
    
  • Uma cadeia de caracteres que contém um URI de um contêiner de blobs do Armazenamento do Microsoft Azure como saída do trabalho. O trabalho cria um blob de blocos nesse contêiner para armazenar informações de erro do trabalho de importação concluído. O token SAS deve incluir estas permissões:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    

Observação

Os dois parâmetros podem apontar para o mesmo contêiner de blobs. Os parâmetros separados simplesmente permitem mais controle sobre seus dados, pois o contêiner de saída requer permissões adicionais.

O snippet de código de C# a seguir mostra como iniciar um trabalho de importação:

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

Esse método também pode ser usado para importar os dados para o dispositivo gêmeo. O formato para a entrada de dados é o mesmo mostrado na seção ExportDevicesAsync. Dessa forma, você pode reimportar os dados exportados.

Comportamento de importação

É possível usar o método ImportDevicesAsync para executar as seguintes operações em massa no registro de identidade:

  • Registro em massa de novos dispositivos
  • Exclusões em massa dos dispositivos existentes
  • Alterações de status em massa (habilitar ou desabilitar dispositivos)
  • Atribuição em massa de novas chaves de autenticação de dispositivo
  • Regeneração automática em massa de chaves de autenticação de dispositivo
  • Atualização em massa de dados gêmeos

É possível executar qualquer combinação das operações anteriores em uma única chamada ImportDevicesAsync . Por exemplo, é possível registrar novos dispositivos e excluir ou atualizar dispositivos existentes ao mesmo tempo. Quando usado com o método ExportDevicesAsync , é possível migrar completamente todos os seus dispositivos de um hub IoT para outro.

Use a propriedade opcional importMode nos dados de serialização de importação para cada dispositivo para controlar o processo de importação por dispositivo. A propriedade importMode tem as seguintes opções:

  • Criar
  • CreateOrUpdate (padrão)
  • CreateOrUpdateIfMatchETag
  • Delete (excluir)
  • DeleteIfMatchETag
  • Atualização
  • UpdateIfMatchETag
  • UpdateTwin
  • UpdateTwinIfMatchETag

Para mais detalhes sobre cada uma dessas opções de modo de importação, confira ImportMode

Solucionar problemas em trabalhos de importação

O uso de um trabalho de importação para criar dispositivos pode falhar com um problema de cota quando estiver próximo ao limite do número de dispositivos do hub IoT. Essa falha pode acontecer mesmo se a contagem total de dispositivos ainda seja menor que o limite de cota. O erro IotHubQuotaExceeded (403002) é retornado com a seguinte mensagem de erro: "O número total de dispositivos no IotHub excedeu a cota alocada".

Se você receber esse erro, poderá usar a consulta a seguir para retornar o número total de dispositivos registrados no hub IoT:

SELECT COUNT() as totalNumberOfDevices FROM devices

Para obter informações sobre o número total de dispositivos que podem ser registrados em um hub IoT, consulte Limites do hub IoT.

Se ainda houver cota disponível, você poderá examinar o blob de saída do trabalho para encontrar dispositivos que falharam com o erro IotHubQuotaExceeded (403002). Em seguida, você pode tentar adicionar esses dispositivos individualmente ao hub IoT. Por exemplo, você pode usar os métodos AddDeviceAsync ou AddDeviceWithTwinAsync. Não tente adicionar os dispositivos usando outro trabalho, pois você pode encontrar o mesmo erro.

Exemplo de importação de dispositivos – provisionamento de dispositivo em massa

O snippet de código C# a seguir, do método GenerateDevicesAsync no exemplo do SDK, ilustra como gerar várias identidades de dispositivo que:

  • Inclua as chaves de autenticação.
  • Grave essas informações de dispositivo em um blob de blocos.
  • Importe os dispositivos para o registro de identidade.
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}.");
}

Exemplo de importação de dispositivos – exclusão em massa

O snippet de código C# a seguir, do método DeleteFromHubAsync no exemplo do SDK, mostra como excluir todos os dispositivos de um 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}");
}

Obter o URI de SAS do contêiner

O exemplo de código a seguir mostra como gerar um URI SAS com as permissões de leitura, gravação e exclusão para um contêiner de blobs:

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

Próximas etapas

Neste artigo, você aprendeu a realizar operações em massa no registro de identidade em um Hub IoT. Muitas dessas operações, incluindo como mover dispositivos de um hub para outro, são usadas na seção Gerenciar dispositivos registrados no hub IoT de Como migrar manualmente um hub IoT do Azure usando um modelo do Azure Resource Manager.