Massimportera och massexportera IoT Hub-enhetsidentiteter

Varje IoT-hubb har ett identitetsregister som du kan använda för att skapa enhetsresurser i tjänsten. Identitetsregistret gör det även möjligt att styra åtkomst till enhetsriktade slutpunkter. Den här artikeln beskriver hur du importerar och exporterar enhetsidentiteter i bulk till och från ett identitetsregister med hjälp av exemplet ImportExportDeviceSample som ingår i Microsoft Azure IoT SDK för .NET. Mer information om hur du kan använda den här funktionen när du migrerar en IoT-hubb till en annan region finns i Så här migrerar du en Azure IoT-hubb manuellt med hjälp av en Azure Resource Manager-mall.

Kommentar

IoT Hub har nyligen lagt till stöd för virtuella nätverk i ett begränsat antal regioner. Den här funktionen skyddar import- och exportåtgärder och eliminerar behovet av att skicka nycklar för autentisering. För närvarande är stöd för virtuella nätverk endast tillgängligt i dessa regioner: WestUS2, EastUS och SouthCentralUS. Mer information om stöd för virtuella nätverk och API-anrop för att implementera det finns i IoT Hub-stöd för virtuella nätverk.

Import- och exportåtgärder utförs i kontexten för jobb som gör att du kan köra masstjänståtgärder mot en IoT-hubb.

Klassen RegistryManager i SDK innehåller metoderna ExportDevicesAsync och ImportDevicesAsync som använder jobbramverket . Med de här metoderna kan du exportera, importera och synkronisera hela IoT Hub-identitetsregistret.

Den här artikeln beskriver hur du använder klassen RegistryManager och jobbsystemet för att utföra massimport och export av enheter till och från en IoT-hubbs identitetsregister. Du kan också använda Azure IoT Hub Device Provisioning Service för att aktivera zero-touch- och just-in-time-etablering till en eller flera IoT-hubbar. Mer information finns i dokumentationen för etableringstjänsten.

Kommentar

Några av kodfragmenten i den här artikeln ingår i tjänstexemplet ImportExportDevicesSample som tillhandahålls med Microsoft Azure IoT SDK för .NET. Exemplet finns i /iothub/service/samples/how to guides/ImportExportDevicesSample mappen för SDK och, där det anges, ingår kodfragment från ImportExportDevicesSample.cs filen för det SDK-exemplet. Mer information om exemplet ImportExportDevicesSample och andra tjänstexempel som ingår i Azure IoT SDK-for.NET finns i Azure IoT Hub-tjänstexempel för C#.

Vad är jobb?

Identitetsregisteråtgärder använder jobbsystemet när åtgärden:

  • Har en potentiellt lång körningstid jämfört med vanliga körningsåtgärder.

  • Returnerar en stor mängd data till användaren.

I stället för ett enda API-anrop som väntar eller blockerar resultatet av åtgärden skapar åtgärden asynkront ett jobb för den IoT-hubben. Åtgärden returnerar sedan omedelbart ett JobProperties-objekt .

Följande C#-kodfragment visar hur du skapar ett exportjobb:

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

Kommentar

Om du vill använda klassen RegistryManager i C#-koden lägger du till NuGet-paketet Microsoft.Azure.Devices i projektet. Klassen RegistryManager finns i namnområdet Microsoft.Azure.Devices .

Du kan använda klassen RegistryManager för att fråga efter tillståndet för jobbet med hjälp av de returnerade JobProperties-metadata . Om du vill skapa en instans av klassen RegistryManager använder du metoden CreateFrom Anslut ionString.

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

Så här hittar du anslutningssträng för din IoT-hubb i Azure-portalen:

  1. Gå till IoT-hubben.

  2. Välj Principer för delad åtkomst.

  3. Välj en princip med hänsyn till de behörigheter du behöver.

  4. Kopiera anslutningssträng för den principen.

Följande C#-kodfragment från metoden WaitForJobAsync i SDK-exemplet visar hur du avsöker var femte sekund för att se om jobbet har körts klart:

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

Kommentar

Om ditt lagringskonto har brandväggskonfigurationer som begränsar IoT Hub-anslutningen bör du överväga att använda Microsofts betrodda förstapartsundantag (tillgängligt i utvalda regioner för IoT-hubbar med hanterad tjänstidentitet).

Begränsningar för enhetsimport/exportjobb

Endast ett aktivt enhetsimport- eller exportjobb tillåts åt gången för alla IoT Hub-nivåer. IoT Hub har också begränsningar för antalet jobbåtgärder. Mer information finns i IoT Hub-kvoter och begränsning.

Exportera enheter

Använd metoden ExportDevicesAsync för att exportera hela IoT Hub-identitetsregistret till en Azure Storage-blobcontainer med hjälp av en signatur för delad åtkomst (SAS). Med den här metoden kan du skapa tillförlitliga säkerhetskopior av din enhetsinformation i en blobcontainer som du styr.

Metoden ExportDevicesAsync kräver två parametrar:

  • En sträng som innehåller en URI för en blobcontainer. Den här URI:n måste innehålla en SAS-token som ger skrivåtkomst till containern. Jobbet skapar en blockblob i den här containern för att lagra serialiserade exportenhetsdata. SAS-token måste innehålla följande behörigheter:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    
  • Ett booleskt värde som anger om du vill undanta autentiseringsnycklar från dina exportdata. Om det är falskt inkluderas autentiseringsnycklar i exportutdata. Annars exporteras nycklar som null.

Följande C#-kodfragment visar hur du initierar ett exportjobb som innehåller enhetsautentiseringsnycklar i exportdata och sedan söker efter slutförande:

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

Du hittar liknande kod i metoden ExportDevicesAsync från SDK-exemplet. Jobbet lagrar sina utdata i den angivna blobcontainern som en blockblob med namnet devices.txt. Utdata består av JSON-serialiserade enhetsdata med en enhet per rad.

I följande exempel visas utdata:

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

Om en enhet har tvillingdata exporteras även tvillingdata tillsammans med enhetsdata. I följande exempel visas det här formatet. Alla data från raden "twinETag" till slutet är tvillingdata.

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

Om du behöver åtkomst till dessa data i kod kan du deserialisera dessa data med hjälp av klassen ExportImportDevice . Följande C#-kodfragment från metoden ReadFromBlobAsync i SDK-exemplet visar hur du läser enhetsinformation som tidigare exporterades från ExportImportDevice till en BlobClient-instans :

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

Importera enheter

Med metoden ImportDevicesAsync i klassen RegistryManager kan du utföra massimport- och synkroniseringsåtgärder i ett IoT Hub-identitetsregister. Precis som metoden ExportDevicesAsync använder metoden ImportDevicesAsync jobbramverket.

Var försiktig med metoden ImportDevicesAsync eftersom den förutom att etablera nya enheter i identitetsregistret även kan uppdatera och ta bort befintliga enheter.

Varning

Det går inte att ångra en importåtgärd. Säkerhetskopiera alltid dina befintliga data med metoden ExportDevicesAsync till en annan blobcontainer innan du gör massändringar i identitetsregistret.

Metoden ImportDevicesAsync tar två parametrar:

  • En sträng som innehåller en URI för en Azure Storage-blobcontainer som ska användas som indata till jobbet. Den här URI:n måste innehålla en SAS-token som ger läsbehörighet till containern. Den här containern måste innehålla en blob med namnet devices.txt som innehåller serialiserade enhetsdata som ska importeras till ditt identitetsregister. Importdata måste innehålla enhetsinformation i samma JSON-format som ExportImportDevice-jobbet använder när det skapar en devices.txt blob. SAS-token måste innehålla följande behörigheter:

    SharedAccessBlobPermissions.Read
    
  • En sträng som innehåller en URI för en Azure Storage-blobcontainer som ska användas som utdata från jobbet. Jobbet skapar en blockblob i den här containern för att lagra eventuell felinformation från det slutförda importjobbet. SAS-token måste innehålla följande behörigheter:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    

Kommentar

De två parametrarna kan peka på samma blobcontainer. De separata parametrarna ger helt enkelt mer kontroll över dina data eftersom utdatacontainern kräver ytterligare behörigheter.

Följande C#-kodfragment visar hur du initierar ett importjobb:

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

Den här metoden kan också användas för att importera data för enhetstvillingen. Formatet för dataindata är samma som det format som visas i avsnittet ExportDevicesAsync . På så sätt kan du importera om exporterade data.

Importbeteende

Du kan använda metoden ImportDevicesAsync för att utföra följande massåtgärder i identitetsregistret:

  • Massregistrering av nya enheter
  • Massborttagning av befintliga enheter
  • Massstatusändringar (aktivera eller inaktivera enheter)
  • Masstilldelning av nya enhetsautentiseringsnycklar
  • Massåtergenerering av enhetsautentiseringsnycklar
  • Massuppdatering av tvillingdata

Du kan utföra valfri kombination av föregående åtgärder i ett enda ImportDevicesAsync-anrop . Du kan till exempel registrera nya enheter och ta bort eller uppdatera befintliga enheter samtidigt. När du använder tillsammans med metoden ExportDevicesAsync kan du helt migrera alla dina enheter från en IoT-hubb till en annan.

Använd den valfria egenskapen importMode i import-serialiseringsdata för varje enhet för att styra importprocessen per enhet. Egenskapen importMode har följande alternativ:

  • Skapa
  • CreateOrUpdate (standard)
  • CreateOrUpdateIfMatchETag
  • Ta bort
  • DeleteIfMatchETag
  • Uppdatering
  • UpdateIfMatchETag
  • UpdateTwin
  • UpdateTwinIfMatchETag

Mer information om vart och ett av dessa importlägesalternativ finns i ImportMode

Felsöka importjobb

Att använda ett importjobb för att skapa enheter kan misslyckas med ett kvotproblem när det är nära gränsen för antal enheter i IoT-hubben. Det här felet kan inträffa även om det totala antalet enheter fortfarande är lägre än kvotgränsen. Felet IotHubQuotaExceeded (403002) returneras med följande felmeddelande: "Totalt antal enheter på IotHub överskred den allokerade kvoten."

Om du får det här felet kan du använda följande fråga för att returnera det totala antalet enheter som registrerats på din IoT-hubb:

SELECT COUNT() as totalNumberOfDevices FROM devices

Information om det totala antalet enheter som kan registreras till en IoT-hubb finns i IoT Hub-gränser.

Om det fortfarande finns en kvot kan du undersöka jobbutdatabloben för enheter som misslyckades med felet IotHubQuotaExceeded (403002). Du kan sedan prova att lägga till dessa enheter individuellt i IoT-hubben. Du kan till exempel använda metoderna AddDeviceAsync eller AddDeviceWithTwinAsync . Försök inte lägga till enheterna med ett annat jobb eftersom du kan stöta på samma fel.

Exempel på importenheter – massetablering av enheter

Följande C#-kodfragment från metoden GenerateDevicesAsync i SDK-exemplet visar hur du genererar flera enhetsidentiteter som:

  • Inkludera autentiseringsnycklar.
  • Skriv den enhetsinformationen till en blockblob.
  • Importera enheterna till identitetsregistret.
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}.");
}

Exempel på importenheter – massborttagning

Följande C#-kodfragment från metoden DeleteFromHubAsync i SDK-exemplet visar hur du tar bort alla enheter från en IoT-hubb:

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

Hämta containerns SAS-URI

Följande kodexempel visar hur du genererar en SAS-URI med läs-, skriv- och borttagningsbehörigheter för en blobcontainer:

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

Nästa steg

I den här artikeln har du lärt dig hur du utför massåtgärder mot identitetsregistret i en IoT-hubb. Många av dessa åtgärder, inklusive hur du flyttar enheter från en hubb till en annan, används i avsnittet Hantera enheter som är registrerade i IoT Hub i Så här migrerar du en Azure IoT-hubb manuellt med hjälp av en Azure Resource Manager-mall.