Import and export IoT Hub device identities in bulk (Импорт и экспорт удостоверений устройств центра Интернета вещей в пакетном режиме)

Каждый Центр Интернета вещей имеет реестр удостоверений, который можно использовать для создания ресурсов устройств в службе. Реестр удостоверений обеспечивает доступ к конечным точкам, обращенным к устройствам. В этой статье описывается, как импортировать и экспортировать удостоверения устройств массово в реестр удостоверений и из него, используя пример ImportExportDeviceSample, включенный в пакет SDK Для Интернета вещей Microsoft Azure для .NET. Дополнительные сведения об использовании этой возможности при переносе центра Интернета вещей в другой регион см. в статье "Как вручную перенести центр Интернета вещей Azure с помощью шаблона Azure Resource Manager".

Примечание.

Центр Интернета вещей недавно добавлена поддержка виртуальной сети в ограниченном количестве регионов. Эта функция защищает операции импорта и экспорта и устраняет необходимость передавать ключи для аутентификации. В настоящее время поддержка виртуальной сети доступна только в этих регионах: WestUS2, EastUS и SouthCentralUS. Дополнительные сведения о поддержке виртуальных сетей и вызовах API для ее реализации см. в разделе Поддержка центра Интернета вещей для виртуальных сетей.

Операции импорта и экспорта выполняются в контексте заданий, позволяющих выполнять операции массового обслуживания в центре Интернета вещей.

Класс RegistryManager в пакете SDK включает методы ExportDevicesAsync и ImportDevicesAsync, использующие платформу заданий. С помощью этих методов можно экспортировать, импортировать и синхронизировать весь реестр удостоверений Центра Интернета вещей.

В этой статье рассматривается использование класса RegistryManager и системы заданий для выполнения массового импорта и экспорта устройств в реестр удостоверений Центра Интернета вещей и из нее. Вы также можете использовать службу подготовки устройств Центр Интернета вещей Azure, чтобы включить JIT-подготовку к одному или нескольким центрам Интернета вещей. Дополнительные сведения см. в документации по подготовке службы.

Примечание.

Некоторые фрагменты кода в этой статье включены из примера службы ImportExportDevicesSample, предоставленного пакетом SDK Для Интернета вещей Microsoft Azure для .NET. Пример находится в /iothub/service/samples/how to guides/ImportExportDevicesSample папке пакета SDK и, где указано, фрагменты кода включаются из ImportExportDevicesSample.cs файла для этого примера пакета SDK. Дополнительные сведения о примере ImportExportDevicesSample и других примерах служб, включенных в for.NET пакета SDK Для Интернета вещей Azure, см. в примерах служб Центра Интернета вещей Azure для C#.

Что такое задания?

Операции реестра удостоверений используют систему заданий при выполнении операции:

  • характеризуется длительным временем выполнения по сравнению со стандартными операциями среды выполнения или же

  • возвращает пользователю большой объем данных.

Вместо одного вызова API, ожидающего или блокирующего результат операции, операция асинхронно создает задание для этого центра Интернета вещей. и сразу же возвращает объект JobProperties.

В следующем фрагменте кода C# показано, как создать задание экспорта.

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

Примечание.

Чтобы использовать класс RegistryManager в коде C#, добавьте в проект пакет NuGet Microsoft.Azure.Devices. Класс RegistryManager находится в пространстве имен Microsoft.Azure.Devices.

Вы можете использовать класс RegistryManager для запросов состояния задания с помощью возвращенных метаданных JobProperties. Чтобы создать экземпляр класса RegistryManager, используйте метод CreateFromConnectionString.

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

Чтобы найти строку подключения для Центра Интернета вещей, сделайте следующее на портале Azure:

  1. Перейдите в Центр Интернета вещей.

  2. Выберите Политики общего доступа.

  3. Выберите политику, учитывая необходимые разрешения.

  4. Скопируйте строка подключения для этой политики.

В следующем фрагменте кода C# из метода WaitForJobAsync в примере пакета SDK показано, как провести опрос каждые пять секунд, чтобы узнать, завершено ли задание:

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

Примечание.

Если в вашей учетной записи хранения есть конфигурации брандмауэра, которые ограничивают возможность подключения к Центру Интернета вещей, рассмотрите возможность использования Доверенного первого исключения Майкрософт (доступно в некоторых регионах для концентраторов Интернета вещей с управляемым удостоверением службы).

Пределы заданий импорта/экспорта устройств

Одновременно для всех уровней Центр Интернета вещей разрешено только одно активное задание импорта или экспорта устройств. Центр Интернета вещей также имеет ограничения на скорость выполнения заданий. Дополнительные сведения см. в статье Квоты и регулирование Центра Интернета вещей.

Экспорт устройств

Используйте метод ExportDevicesAsync, чтобы полностью экспортировать реестр удостоверений центра Интернета вещей в контейнер больших двоичных объектов службы хранилища Azure с помощью подписи общего доступа (SAS). Этот метод позволяет создавать надежные резервные копии данных устройства в контейнере BLOB-объектов, которым вы управляете.

Для использования метода ExportDevicesAsync требуется два параметра.

  • Строковый, содержащий URI контейнера больших двоичных объектов. Этот URI должен содержать маркер SAS, который предоставляет доступ на запись в контейнер. Задание создает в этом контейнере блочный BLOB-объект для хранения сериализованных данных экспорта устройств. Маркер SAS должен включать следующие разрешения.

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    
  • Логический, указывающий, следует ли исключить из данных экспорта ключи проверки подлинности. Если задано значение false, ключи аутентификации включены в выходные данные экспорта. В противном случае экспортируются ключи со значением null.

В следующем фрагменте кода C# показано, как запустить задание экспорта, содержащее ключи проверки подлинности устройства в данных экспорта, а затем выполнить запрос данных о завершении:

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

Аналогичный код можно найти в методе ExportDevicesAsync из примера пакета SDK. Задание сохраняет выходные данные в указанном контейнере BLOB-объектов в виде блочного BLOB-объекта с именем devices.txt. Выходные данные состоят из сериализованных данных устройств JSON с одним устройством в строке.

В следующем примере показаны выходные данные.

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

При наличии данных двойника устройства они также будут экспортированы вместе с данными устройства. Этот формат приведен в примере ниже. Все данные, начиная со строки "twinETag" и до конца, — это данные двойника устройства.

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

Если вам нужен доступ к этим данным в коде, вы можете десериализировать эти данные с помощью класса ExportImportDevice . Следующий фрагмент кода C# из метода ReadFromBlobAsync в примере пакета SDK показывает, как считывать сведения об устройстве, которые ранее экспортировались из ExportImportDevice в экземпляр 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;
}

Импорт устройств

Метод ImportDevicesAsync в классе RegistryManager позволяет выполнять операции массового импорта и синхронизации в реестре удостоверений Центра Интернета вещей. Как и метод ExportDevicesAsync, метод ImportDevicesAsync использует платформу заданий.

Использовать метод ImportDevicesAsync следует с осторожностью, так как наряду с подготовкой новых устройств в реестре удостоверений он также может обновлять и удалять существующие устройства.

Предупреждение

Операцию импорта отменить нельзя. Прежде чем вносить массовые изменения в реестр удостоверений, всегда необходимо создавать резервную копию существующих данных в другом контейнере больших двоичных объектов с помощью метода ExportDevicesAsync.

Метод ImportDevicesAsync принимает два следующих параметра.

  • Строка, содержащая универсальный код ресурса (URI) контейнера больших двоичных объектов служба хранилища Azure, который будет использоваться в качестве входных данных для задания. Этот URI должен содержать маркер SAS, который предоставляет доступ на чтение контейнера. Этот контейнер должен включать большой двоичный объект с именем devices.txt, содержащий сериализованные данные устройств для импорта в реестр удостоверений. Импортируемые данные должны включать в себя сведения об устройствах в том же формате JSON, который задание ExportImportDevice использует при создании большого двоичного объекта devices.txt. Маркер SAS должен включать следующие разрешения.

    SharedAccessBlobPermissions.Read
    
  • Строка, содержащая универсальный код ресурса (URI) контейнера больших двоичных объектов служба хранилища Azure для использования в качестве выходных данных из задания. Задание создает блочный большой двоичный объект в этом контейнере, чтобы сохранить все сведения об ошибках из завершенного задания импорта. Маркер SAS должен включать следующие разрешения.

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    

Примечание.

Два параметра могут указывать на один и тот же контейнер BLOB-объектов. Отдельные параметры предоставляют больший уровень контроля над выходными данными, так как для выходного контейнера требуются дополнительные разрешения.

В следующем фрагменте кода C# показано, как инициировать задание импорта.

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

Вы можете использовать этот метод, чтобы импортировать данные для двойника устройства. Для входных данных используется тот же формат, который был показан в разделе ExportDevicesAsync. Таким образом, экспортированные данные можно импортировать повторно.

Поведение при импорте

Метод ImportDevicesAsync можно использовать для выполнения следующих массовых операций в реестре удостоверений.

  • Массовая регистрация новых устройств
  • Массовое удаление существующих устройств
  • Массовое изменение состояния (включение или отключение устройств)
  • Массовое назначение новых ключей проверки подлинности устройств
  • Массовая автоматическая повторность ключей проверки подлинности устройства
  • Массовое обновление данных двойника устройства.

Один вызов метода ImportDevicesAsync позволяет выполнять любое сочетание приведенных выше операций. Например, можно регистрировать новые устройства и одновременно удалять или обновлять существующие. Если этот метод использовать вместе с методом ExportDevicesAsync , можно полностью перенести все устройства из одного центра IoT в другой.

Управление процессом импорта на уровне каждого устройства осуществляется с помощью необязательного свойства importMode в сериализованных данных импорта для каждого устройства. Свойство importMode имеет следующие параметры:

  • Создание
  • CreateOrUpdate (по умолчанию)
  • CreateOrUpdateIfMatchETag
  • Удалить
  • DeleteIfMatchETag
  • Update
  • UpdateIfMatchETag
  • UpdateTwin
  • UpdateTwinIfMatchETag

Дополнительные сведения о каждом из этих параметров режима импорта см. в разделе ImportMode

Устранение неполадок с заданиями импорта

Использование задания импорта для создания устройств может завершиться ошибкой квоты, когда оно близко к ограничению количества устройств Центра Интернета вещей. Этот сбой может произойти, даже если общее число устройств по-прежнему ниже предела квоты. Ошибка IotHubQuotaExceeded (403002) возвращается со следующим сообщением: "Общее количество устройств в Центре Интернета вещей превысило выделенную квоту".

При возникновении этой ошибки можно использовать следующий запрос, чтобы получить общее количество устройств, зарегистрированных в Центре Интернета вещей:

SELECT COUNT() as totalNumberOfDevices FROM devices

Сведения об общем количестве устройств, которые можно зарегистрировать в Центре Интернета вещей, см. в Ограничения Центра Интернета вещей.

Если квота по-прежнему доступна, можно проверить BLOB-объект выходных данных задания для получения информации об устройствах, на которых произошел сбой с ошибкой IotHubQuotaExceeded (403002). Затем можно попробовать добавить эти устройства по отдельности в Центр Интернета вещей. Например, можно использовать методы AddDeviceAsync или AddDeviceWithTwinAsync. Не пытайтесь добавить устройства с помощью другого задания, так как может возникнуть та же ошибка.

Пример импорта устройств — массовая подготовка устройств

Следующий фрагмент кода C# из метода GenerateDevicesAsync в примере пакета SDK иллюстрирует создание нескольких удостоверений устройств, которые:

  • включают в себя ключи проверки подлинности;
  • записывают сведения об этом устройстве в блочный BLOB-объект;
  • импортируют устройства в реестр удостоверений.
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}.");
}

Пример импорта устройств — массовое удаление

В следующем фрагменте кода C# из метода DeleteFromHubAsync в примере пакета SDK показано, как удалить все устройства из центра Интернета вещей:

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

Получение URI SAS контейнера

В следующем примере кода показано, как создать URI SAS с разрешениями на чтение, запись и удаление контейнера больших двоичных объектов.

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

Следующие шаги

В этой статье вы узнали, как выполнять массовые операции с реестром удостоверений в Центре Интернета вещей. Многие из этих операций, включая перемещение устройств из одного центра в другой, используются в разделе "Управление устройствами, зарегистрированными в Центре Интернета вещей" раздела "Как вручную перенести центр Интернета вещей Azure" с помощью шаблона Azure Resource Manager.