共用方式為


大量匯入和匯出 IoT 中樞裝置身分識別

每個 IoT 中樞都有一個身分識別登錄,您可用來在服務中建立裝置的資源。 身分識別登錄也可讓您控制裝置面向端點的存取權。 本文說明如何使用 Microsoft Azure IoT SDK for .NET 隨附的 ImportExportDeviceSample 範例,大量匯入和匯出身分識別登錄中的裝置身分識別。 如需在將 IoT 中樞移轉至不同區域時如何使用這項功能的詳細資訊,請參閱如何使用 Azure Resource Manager 範本手動移轉 Azure IoT 中樞

注意

IoT 中樞最近在有限的區域中新增了虛擬網路支援。 此功能可保護匯入和匯出作業,不需要傳遞金鑰即可驗證。 目前,虛擬網路支援僅適用於這些區域:WestUS2EastUSSouthCentralUS。 若要深入了解虛擬網路支援,以及實作的 API 呼叫,請參閱虛擬網路的 IoT 中樞支援

匯入和匯出操作會在「作業」的內容中進行,其可讓您對 IoT 中樞執行大量服務操作。

SDK 中的 RegistryManager 類別包含使用作業架構的 ExportDevicesAsyncImportDevicesAsync 方法。 這些方法可讓您匯出、匯入和同步處理整個 IoT 中樞身分識別登錄。

本文討論使用 RegistryManager 類別和作業系統來執行將裝置大量匯入和匯出 IoT 中樞的身分識別登錄。 您也可以使用 Azure IoT 中樞裝置佈建服務,對一或多個 IoT 中樞進行零接觸的 Just-In-Time 自動佈建。 若要深入了解,請參閱佈建服務文件

注意

本文中的部分程式碼片段包含在 Microsoft Azure IoT SDK for .NET 提供的 ImportExportDevicesSample 服務範例中。 此範例位於SDK 的 /iothub/service/samples/how to guides/ImportExportDevicesSample 資料夾中,其中指定了該 SDK 範例的 ImportExportDevicesSample.cs 檔案中包含程式碼片段。 如需 ImportExportDevicesSample 範例和 Azure IoT SDK for.NET 中所包含其他服務範例的詳細資訊,請參閱適用於 C# 的 Azure IoT 中樞服務範例

什麼是作業?

身分識別登錄操作會使用作業系統的前提是操作符合下列條件時:

  • 相較於標準執行階段作業,執行時間可能很長。

  • 會傳回大量資料給使用者。

與其讓單一 API 呼叫等候或封鎖操作的結果,操作會以非同步方式建立該 IoT 中樞的作業。 然後操作會立即傳回 JobProperties 物件。

下列 C# 程式碼片段示範如何建立匯出作業:

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

注意

若要在 C# 程式碼中使用 RegistryManager 類別,請將 Microsoft.Azure.Devices NuGet 套件新增至您的專案。 RegistryManager 類別位於 Microsoft.Azure.Devices 命名空間。

您可以使用 RegistryManager 類別來查詢作業的狀態 (使用所傳回的 JobProperties 中繼資料)。 若要建立 RegistryManager 類別的執行個體,請使用 CreateFromConnectionString 方法。

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

若要尋找 IoT 中樞的連接字串,請在 Azure 入口網站中:

  1. 瀏覽至您的 IoT 中樞。

  2. 選取 [共用存取原則]

  3. 選取原則,並將您需要的權限列入考量。

  4. 複製該原則的連接字串。

下列 C# 程式碼片段來自SDK 範例中的 WaitForJobAsync 方法,示範如何每隔五秒輪詢一次,以查看作業是否已完成執行:

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

注意

如果您的儲存體帳戶具有限制 IoT 中樞連線的防火牆設定,請考慮使用 Microsoft 信任的第一方例外狀況 (適用於具有受管理的服務識別的 IoT 中樞選取區域中)。

裝置匯入/匯出作業限制

所有 IoT 中樞層一次只允許 1 個作用中的裝置匯入或匯出作業。 IoT 中樞也有作業速率的限制。 若要深入瞭解,請參閱 IoT 中樞配額和節流

匯出裝置

使用 ExportDevicesAsync 方法,將整個 IoT 中樞身分識別登錄匯出到使用共用存取簽章 (SAS) 的 Azure 儲存體 Blob 容器。 這個方法可讓您在所控制的 Blob 容器中建立可靠的裝置資訊備份。

ExportDevicesAsync 方法需要兩個參數:

  • 包含 Blob 容器 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));
}

您可以在 SDK 範例的 ExportDevicesAsync 方法中找到類似的程式碼。 作業會在所提供的 Blob 容器中將其輸出儲存為名稱是 devices.txt的區塊 Blob。 輸出資料包含 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# 程式碼片段來自SDK 範例中的 ReadFromBlobAsync 方法,示範如何讀取先前從 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;
}

匯入裝置

RegistryManager 類別中的 ImportDevicesAsync 方法可讓您在 IoT 中樞身分識別登錄中執行大量匯入和同步處理操作。 與 ExportDevicesAsync 方法相同,ImportDevicesAsync 方法會使用作業架構。

請謹慎使用 ImportDevicesAsync 方法,因為除了在身分識別登錄中佈建新裝置外,此方法也會更新和刪除現有裝置。

警告

匯入操作是無法復原的。 請一律先使用 ExportDevicesAsync 方法將現有資料備份到另一個 Blob 容器,再對身分識別登錄進行大量變更。

ImportDevicesAsync 方法會採用兩個參數:

  • 包含 Azure 儲存體 Blob 容器 URI 以作為作業「輸入」的「字串」。 此 URI 必須包含可授與容器讀取權限的 SAS 權杖。 此容器必須包含名稱為 devices.txt 的 Blob,而此 Blob 包含要匯入到身分識別登錄的序列化裝置資料。 匯入資料必須包含 ExportImportDevice 作業建立 devices.txt Blob 時所使用之相同 JSON 格式的裝置資訊。 SAS 權杖必須包含這些權限:

    SharedAccessBlobPermissions.Read
    
  • 包含 Azure 儲存體 Blob 容器 URI 以作為作業「輸出」的「字串」。 作業會在此容器中建立區塊 Blob,以儲存來自已完成之匯入作業的任何錯誤資訊。 SAS 權杖必須包含這些權限:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    

注意

這兩個參數可以指向相同的 Blob 容器。 參數不同只會讓您更能掌控資料,因為輸出容器需要其他權限。

下列 C# 程式碼片段示範如何啟動匯入作業:

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

這個方法也可用來匯入裝置對應項的資料。 資料輸入的格式與 ExportDevicesAsync 的區段中顯示的格式相同。 如此一來,您可以重新匯入已匯出的資料。

匯入行為

您可以使用 ImportDevicesAsync 方法,在您的身分識別登錄中執行下列大量作業:

  • 大量註冊新裝置
  • 大量刪除現有裝置
  • 大量變更狀態 (啟用或停用裝置)
  • 大量指派新的裝置驗證金鑰
  • 大量自動重新產生裝置驗證金鑰
  • 大量更新對應項資料

您可以在單一 ImportDevicesAsync 呼叫中執行上述作業的任意組合。 比方說,您可以同時間註冊新裝置並刪除或更新現有裝置。 搭配 ExportDevicesAsync 方法一起使用時,您可以將某個 IoT 中樞內的所有裝置移轉到另一個 IoT 中樞。

在每個裝置的匯入序列化資料中使用選擇性的 importMode 屬性控制每個裝置的匯入程序。 ImportMode 屬性具有下列選項:

  • 建立
  • CreateOrUpdate (預設值)
  • CreateOrUpdateIfMatchETag
  • 刪除
  • DeleteIfMatchETag
  • 更新
  • UpdateIfMatchETag
  • UpdateTwin
  • UpdateTwinIfMatchETag

如需這些匯入模式選項的詳細資料,請參閱 ImportMode

針對匯入作業進行疑難排解

當配額接近 IoT 中樞的裝置計數限制時,使用匯入作業來建立裝置可能會因為配額問題失敗。 即使裝置計數總計仍然低於配額限制,也會發生這種失敗。 IotHubQuotaExceeded (403002) 錯誤會傳回並顯示下列錯誤訊息:「IotHub 上的裝置總數超過配置的配額」。

如果您收到此錯誤,則可使用下列查詢來傳回 IoT 中樞上註冊的裝置總數:

SELECT COUNT() as totalNumberOfDevices FROM devices

如需可註冊至 IoT 中樞的裝置總數資訊,請參閱 IoT 中樞限制

如果仍有可用的配額,您可以檢查作業輸出 Blob,是否有因為 IotHubQuotaExceeded (403002) 錯誤失敗的裝置。 然後,您可以嘗試將這些裝置個別地新增至 IoT 中樞。 例如,您可以使用 AddDeviceAsyncAddDeviceWithTwinAsync 方法。 請勿嘗試使用其他作業來新增裝置,因為您可能會遇到相同的錯誤。

匯入裝置範例 - 大量裝置佈建

下列 C# 程式碼片段來自SDK 範例中的 GenerateDevicesAsync 方法,說明如何產生多個裝置身分識別:

  • 包含驗證金鑰。
  • 將該裝置資訊寫入至區塊 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# 程式碼片段來自SDK 範例中的 DeleteFromHubAsync 方法,示範如何從 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}");
}

取得容器 SAS URI

下列程式碼範例示範如何產生具有 Blob 容器之讀取、寫入和刪除權限的 SAS URI

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

下一步

在本文中,您已了解如何對 IoT 中樞內的身分識別登錄執行大量操作。 其中許多作業,包括如何將裝置從一個中樞移至另一個中樞,都會在如何使用 Azure Resource Manager 範本手動移轉 Azure IoT 中樞管理註冊到 IoT 中樞的裝置一節中使用。