public async Task ExportDevicesAsync(string hubConnectionString, string blobContainerUri, string userDefinedManagedIdentityResourceId = null) { using RegistryManager srcRegistryManager = RegistryManager.CreateFromConnectionString(hubConnectionString); // If StorageAuthenticationType is set to IdentityBased and userAssignedIdentity property is // not null, the jobs will use user defined managed identity. If the IoT hub is not // configured with the user defined managed identity specified in userAssignedIdentity, // the job will fail. // If StorageAuthenticationType is set to IdentityBased and userAssignedIdentity property is // null, the jobs will use system defined identity by default. If the IoT hub is configured with the // system defined managed identity, the job will succeed but will not use the user defined managed identity. // If the IoT hub is not configured with system defined managed identity, the job will fail. // If StorageAuthenticationType is set to IdentityBased and neither user defined nor system defined // managed identities are configured on the hub, the job will fail. JobProperties jobProperties = JobProperties.CreateForExportJob( outputBlobContainerUri: blobContainerUri, excludeKeysInExport: false, storageAuthenticationType: StorageAuthenticationType.IdentityBased, identity: new ManagedIdentity { userAssignedIdentity = userDefinedManagedIdentityResourceId }); JobProperties jobResult = await srcRegistryManager .ExportDevicesAsync(jobProperties); // Poll every 5 seconds to see if the job has finished executing. while (true) { jobResult = await srcRegistryManager.GetJobAsync(jobResult.JobId); if (jobResult.Status == JobStatus.Completed) { break; } else if (jobResult.Status == JobStatus.Failed) { throw new Exception("Export job failed."); } else if (jobResult.Status == JobStatus.Cancelled) { throw new Exception("Export job was canceled."); } else { await Task.Delay(TimeSpan.FromSeconds(5)); } } }
public async Task RegistryManager_ExportDevices(StorageAuthenticationType storageAuthenticationType) { // arrange StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ExportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); _log.WriteLine($"Using deviceId {deviceId}"); try { string containerName = StorageContainer.BuildContainerName(nameof(RegistryManager_ExportDevices)); storageContainer = await StorageContainer .GetInstanceAsync(containerName) .ConfigureAwait(false); _log.WriteLine($"Using container {storageContainer.Uri}"); Uri containerUri = storageAuthenticationType == StorageAuthenticationType.KeyBased ? storageContainer.SasUri : storageContainer.Uri; await registryManager .AddDeviceAsync( new Device(deviceId) { Authentication = new AuthenticationMechanism { Type = AuthenticationType.Sas }, }) .ConfigureAwait(false); // act JobProperties exportJobResponse = null; int tryCount = 0; while (true) { try { exportJobResponse = await registryManager .ExportDevicesAsync( JobProperties.CreateForExportJob( containerUri.ToString(), true, null, storageAuthenticationType)) .ConfigureAwait(false); break; } // Concurrent jobs can be rejected, so implement a retry mechanism to handle conflicts with other tests catch (JobQuotaExceededException) when(++tryCount < MaxIterationWait) { _log.WriteLine($"JobQuotaExceededException... waiting."); await Task.Delay(s_waitDuration).ConfigureAwait(false); continue; } } // wait for job to complete for (int i = 0; i < MaxIterationWait; ++i) { await Task.Delay(s_waitDuration).ConfigureAwait(false); exportJobResponse = await registryManager.GetJobAsync(exportJobResponse.JobId).ConfigureAwait(false); _log.WriteLine($"Job {exportJobResponse.JobId} is {exportJobResponse.Status} with progress {exportJobResponse.Progress}%"); if (!s_incompleteJobs.Contains(exportJobResponse.Status)) { break; } } // assert exportJobResponse.Status.Should().Be(JobStatus.Completed, "Otherwise import failed"); exportJobResponse.FailureReason.Should().BeNullOrEmpty("Otherwise import failed"); string content = await DownloadFileAsync(storageContainer).ConfigureAwait(false); string[] serializedDevices = content.Split(s_newlines, StringSplitOptions.RemoveEmptyEntries); bool foundDeviceInExport = false; foreach (string serializedDeivce in serializedDevices) { // The first line may be a comment to the user, so skip any lines that don't start with a json object initial character: curly brace if (serializedDeivce[0] != '{') { continue; } ExportImportDevice device = JsonConvert.DeserializeObject <ExportImportDevice>(serializedDeivce); if (StringComparer.Ordinal.Equals(device.Id, deviceId)) { _log.WriteLine($"Found device in export as [{serializedDeivce}]"); foundDeviceInExport = true; break; } } foundDeviceInExport.Should().BeTrue("Expected device did not appear in the export"); } finally { try { storageContainer?.Dispose(); await registryManager.RemoveDeviceAsync(deviceId).ConfigureAwait(false); } catch { } } }
private async Task CleanupDevices() { Console.WriteLine($"Using storage container {_blobContainerClient.Name}" + $" for exporting devices identities to and importing device identities from."); // Retrieve the SAS Uri that will be used to grant access to the storage containers. string storageAccountSasUri = GetStorageAccountSasUriForCleanupJob(_blobContainerClient).ToString(); // Step 1: Export all device identities. JobProperties exportAllDevicesProperties = JobProperties .CreateForExportJob( outputBlobContainerUri: storageAccountSasUri, excludeKeysInExport: true, storageAuthenticationType: StorageAuthenticationType.KeyBased); JobProperties exportAllDevicesJob = null; int tryCount = 0; while (true) { try { exportAllDevicesJob = await _registryManager.ExportDevicesAsync(exportAllDevicesProperties); break; } // Wait for pending jobs to finish. catch (JobQuotaExceededException) when(++tryCount < MaxIterationWait) { Console.WriteLine($"JobQuotaExceededException... waiting."); await Task.Delay(WaitDuration); } } if (exportAllDevicesJob == null) { throw new Exception("Export devices job failed."); } // Wait until the export job is finished. while (true) { exportAllDevicesJob = await _registryManager.GetJobAsync(exportAllDevicesJob.JobId); if (s_completedJobs.Contains(exportAllDevicesJob.Status)) { // Job has finished executing. break; } Console.WriteLine($"Job {exportAllDevicesJob.JobId} is {exportAllDevicesJob.Status} with progress {exportAllDevicesJob.Progress}%"); await Task.Delay(s_waitDuration); } Console.WriteLine($"Job {exportAllDevicesJob.JobId} is {exportAllDevicesJob.Status}."); if (exportAllDevicesJob.Status != JobStatus.Completed) { throw new Exception("Exporting devices failed, exiting."); } // Step 2: Download the exported devices list from the blob create in Step 1. BlobClient blobClient = _blobContainerClient.GetBlobClient(ImportExportDevicesFileName); BlobDownloadInfo download = await blobClient.DownloadAsync(); IEnumerable <ExportImportDevice> exportedDevices = ImportExportDevicesHelpers.BuildExportImportDeviceFromStream(download.Content); // Step 3: Collect the devices that need to be deleted and update their ImportMode to be Delete. // Thie step will create an ExportImportDevice identity for each device/ module identity registered on hub. // If you hub instance has IoT Hub module or Edge module instances registered, then they will be counted as separate entities // from the corresponding IoT Hub device/ Edge device that they are associated with. // As a result, the count of ExportImportDevice identities to be deleted might be greater than the // count of IoT hub devices retrieved in PrintDeviceCountAsync(). var devicesToBeDeleted = new List <ExportImportDevice>(); foreach (var device in exportedDevices) { string deviceId = device.Id; foreach (string prefix in _deleteDevicesWithPrefix) { if (deviceId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { devicesToBeDeleted.Add(device); } } } devicesToBeDeleted .ForEach(device => device.ImportMode = ImportMode.Delete); Console.WriteLine($"Retrieved {devicesToBeDeleted.Count} devices for deletion."); if (devicesToBeDeleted.Count > 0) { // Step 3a: Write the new import data back to the blob. using Stream devicesFile = ImportExportDevicesHelpers.BuildDevicesStream(devicesToBeDeleted); await blobClient.UploadAsync(devicesFile, overwrite : true); // Step 3b: Call import using the same blob to delete all devices. JobProperties importDevicesToBeDeletedProperties = JobProperties .CreateForImportJob( inputBlobContainerUri: storageAccountSasUri, outputBlobContainerUri: storageAccountSasUri, storageAuthenticationType: StorageAuthenticationType.KeyBased); JobProperties importDevicesToBeDeletedJob = null; tryCount = 0; while (true) { try { importDevicesToBeDeletedJob = await _registryManager.ImportDevicesAsync(importDevicesToBeDeletedProperties); break; } // Wait for pending jobs to finish. catch (JobQuotaExceededException) when(++tryCount < MaxIterationWait) { Console.WriteLine($"JobQuotaExceededException... waiting."); await Task.Delay(WaitDuration); } } if (importDevicesToBeDeletedJob == null) { throw new Exception("Import devices job failed."); } // Wait until job is finished. while (true) { importDevicesToBeDeletedJob = await _registryManager.GetJobAsync(importDevicesToBeDeletedJob.JobId); if (s_completedJobs.Contains(importDevicesToBeDeletedJob.Status)) { // Job has finished executing. break; } Console.WriteLine($"Job {importDevicesToBeDeletedJob.JobId} is {importDevicesToBeDeletedJob.Status} with progress {importDevicesToBeDeletedJob.Progress}%"); await Task.Delay(s_waitDuration); } Console.WriteLine($"Job {importDevicesToBeDeletedJob.JobId} is {importDevicesToBeDeletedJob.Status}."); } // Step 4: Delete the storage container created. await _blobContainerClient.DeleteAsync(); Console.WriteLine($"Storage container {_blobContainerClient.Name} deleted."); }
private async Task <JobProperties> CreateAndWaitForJobAsync( StorageAuthenticationType storageAuthenticationType, bool isUserAssignedMsi, string devicesFileName, string configsFileName, RegistryManager registryManager, Uri containerUri) { int tryCount = 0; ManagedIdentity identity = isUserAssignedMsi ? new ManagedIdentity { UserAssignedIdentity = TestConfiguration.IoTHub.UserAssignedMsiResourceId } : null; JobProperties exportJobResponse = JobProperties.CreateForExportJob( containerUri.ToString(), true, devicesFileName, storageAuthenticationType, identity); exportJobResponse.IncludeConfigurations = true; exportJobResponse.ConfigurationsBlobName = configsFileName; while (tryCount < MaxIterationWait) { try { exportJobResponse = await registryManager.ExportDevicesAsync(exportJobResponse).ConfigureAwait(false); break; } // Concurrent jobs can be rejected, so implement a retry mechanism to handle conflicts with other tests catch (JobQuotaExceededException) when(++tryCount < MaxIterationWait) { Logger.Trace($"JobQuotaExceededException... waiting."); await Task.Delay(s_waitDuration).ConfigureAwait(false); continue; } } for (int i = 0; i < MaxIterationWait; ++i) { await Task.Delay(s_waitDuration).ConfigureAwait(false); exportJobResponse = await registryManager.GetJobAsync(exportJobResponse.JobId).ConfigureAwait(false); Logger.Trace($"Job {exportJobResponse.JobId} is {exportJobResponse.Status} with progress {exportJobResponse.Progress}%"); if (!s_incompleteJobs.Contains(exportJobResponse.Status)) { break; } } exportJobResponse.Status.Should().Be(JobStatus.Completed, "Otherwise import failed"); exportJobResponse.FailureReason.Should().BeNullOrEmpty("Otherwise import failed"); return(exportJobResponse); }