Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Utilize AsyncPageable for DPS query return types #3176

Merged
merged 37 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5589407
Utilize AsyncPageable for query return types
timtay-microsoft Mar 14, 2023
69248dd
fixup
timtay-microsoft Mar 14, 2023
733fb37
fixup
timtay-microsoft Mar 14, 2023
68f5887
Remove QueryOptions
timtay-microsoft Mar 15, 2023
9f5a565
asd
timtay-microsoft Mar 15, 2023
26b1bf5
cancellation token checks
timtay-microsoft Mar 15, 2023
7051ff1
Apply suggestions from code review
timtay-microsoft Mar 15, 2023
2b7ce27
PR comments
timtay-microsoft Mar 15, 2023
151053f
this works
timtay-microsoft Mar 15, 2023
03ad086
spacing
timtay-microsoft Mar 15, 2023
b436cb4
fix unit test
timtay-microsoft Mar 15, 2023
7299b70
testing something out
timtay-microsoft Mar 15, 2023
6d30af0
test
timtay-microsoft Mar 15, 2023
e58b13f
cr comments
timtay-microsoft Mar 15, 2023
ce9e37f
asdf
timtay-microsoft Mar 15, 2023
8fdf56f
Revert "asdf"
timtay-microsoft Mar 15, 2023
fa242ae
Update migration guide
timtay-microsoft Mar 20, 2023
cbebfe6
so far
timtay-microsoft Mar 20, 2023
eee3572
Merge branch 'previews/v2' of https://github.com/Azure/azure-iot-sdk-…
timtay-microsoft Mar 20, 2023
2d6e116
Merge branch 'timtay/AsyncPageable' of https://github.com/Azure/azure…
timtay-microsoft Mar 20, 2023
35ef6b3
More
timtay-microsoft Mar 20, 2023
6220be0
documentation
timtay-microsoft Mar 20, 2023
15282e9
fixup
timtay-microsoft Mar 21, 2023
bfaad0b
migration doc work
timtay-microsoft Mar 21, 2023
5d7d5dc
Merge branch 'previews/v2' of https://github.com/Azure/azure-iot-sdk-…
timtay-microsoft Mar 21, 2023
4e242b9
adsf
timtay-microsoft Mar 21, 2023
7d0f9f9
fixup
timtay-microsoft Mar 21, 2023
1c794be
fixup
timtay-microsoft Mar 21, 2023
ab66441
adf
timtay-microsoft Mar 21, 2023
4f5cf91
limits
timtay-microsoft Mar 21, 2023
167cc5a
feedback
timtay-microsoft Mar 21, 2023
c54658a
feedback
timtay-microsoft Mar 21, 2023
7ea2df6
Formatting
Mar 22, 2023
f59bd2a
Merge pull request #3185 from Azure/drwill/formatting
Mar 22, 2023
011c080
dispose practices
timtay-microsoft Mar 22, 2023
3048f43
fixup
timtay-microsoft Mar 22, 2023
b190142
fixup
timtay-microsoft Mar 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions SDK v2 migration guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,9 @@ These span across all service clients.
#### Notable breaking changes

- Operations that offer concurrency protection using `ETag`s, now take a parameter `onlyIfUnchanged` that relies on the ETag property of the submitted entity.
- `IotHubServiceClient.Query.CreateAsync<T>(...)` now returns an `AsyncPageable` of the queried results.
- Iterate on entries by using `await foreach (ClientTwin queriedTwin in client.Query.CreateAsync<ClientTwin>(queryText))`.
- Iterate on pages of entries by using `await foreach (Page<ClientTwin> queriedTwinPage in _client.Query.CreateAsync<ClientTwin>(queryText).AsPages())`
- `IotHubServiceClient.Query.Create<T>(...)` now returns an `AsyncPageable` of the queried results.
- Iterate on entries by using `await foreach (ClientTwin queriedTwin in client.Query.Create<ClientTwin>(queryText))`.
- Iterate on pages of entries by using `await foreach (Page<ClientTwin> queriedTwinPage in _client.Query.Create<ClientTwin>(queryText).AsPages())`
- `JobProperties` properties that hold Azure Storage SAS URIs are now of type `System.Uri` instead of `string`.
- `JobProperties` has been split into several classes with only the necessary properties for the specified operation.
- See `ExportJobProperties`, `ImportJobProperties`, and `IotHubJobResponse`.
Expand Down Expand Up @@ -326,7 +326,7 @@ These span across all service clients.
| `DeviceConnectionState` | `ClientConnectionState` | See² |
| `DeviceStatus` | `ClientStatus` | See² |
| `DeviceCapabilities` | `ClientCapabilities` | See² |
| `RegistryManager.CreateQuery(...)` | `IotHubServiceClient.Query.CreateAsync<T>(...)` | |
| `RegistryManager.CreateQuery(...)` | `IotHubServiceClient.Query.Create<T>(...)` | |
| `RegistryManager.AddConfigurationAsync(...)` | `IotHubServiceClient.Configurations.CreateAsync(...)` | |
| `RegistryManager.GetConfigurationsAsync(int maxCount)`| `IotHubServiceClient.Configurations.GetAsync(int maxCount)` | |
| `RegistryManager.RemoveConfigurationAsync(...)` | `IotHubServiceClient.Configurations.DeleteAsync(...)` | |
Expand Down Expand Up @@ -385,7 +385,7 @@ These span across all service clients.
|:---|:---|:---|
| `JobsClient` | `IotHubServiceClient`, subclients `ScheduledJobs` | |
| `JobClient.GetJobAsync(...)` | `IotHubServiceClient.ScheduledJobs.GetAsync(...)` | |
| `JobClient.CreateQuery()` | `IotHubServiceClient.ScheduledJobs.CreateQueryAsync()` | |
| `JobClient.CreateQuery()` | `IotHubServiceClient.ScheduledJobs.CreateQuery()` | |
| `JobsClient.ScheduleTwinUpdateAsync(...)` | `IotHubServiceClient.ScheduledJobs.ScheduledTwinUpdateAsync(...)` | |
| `JobType.ExportDevices` | `JobType.Export` | Matches the actual value expected by the service.¹ |
| `JobType.ImportDevices` | `JobType.Import` | See¹ |
Expand Down Expand Up @@ -449,7 +449,15 @@ These span across all service clients.

#### Notable breaking changes

- Query methods (like for individual and group enrollments) now take a query string (and optionally a page size parameter), and the `Query` result no longer requires disposing.
- `ProvisioningServiceClient.DeviceRegistrationStates.CreateEnrollmentGroupQuery(...)` now returns an `AsyncPageable` of the queried results.
- Iterate on entries by using `await foreach (DeviceRegistrationState registrationState in client.DeviceRegistrationStates.CreateEnrollmentGroupQuery(queryText))`.
- Iterate on pages of entries by using `await foreach (Page<DeviceRegistrationState> registrationStatePage in client.DeviceRegistrationStates.CreateEnrollmentGroupQuery(queryText).AsPages())`
timtay-microsoft marked this conversation as resolved.
Show resolved Hide resolved
- `ProvisioningServiceClient.IndividualEnrollments.CreateQuery(...)` now returns an `AsyncPageable` of the queried results.
- Iterate on entries by using `await foreach (IndividualEnrollment enrollment in client.IndividualEnrollments.CreateQuery(queryText))`.
- Iterate on pages of entries by using `await foreach (Page<IndividualEnrollment> enrollmentsPage in client.IndividualEnrollments.CreateQuery(queryText).AsPages())`
- `ProvisioningServiceClient.EnrollmentGroups.CreateQuery(...)` now returns an `AsyncPageable` of the queried results.
- Iterate on entries by using `await foreach (EnrollmentGroup enrollment in client.EnrollmentGroups.CreateQuery(queryText))`.
- Iterate on pages of entries by using `await foreach (Page<EnrollmentGroup> enrollmentsPage in client.EnrollmentGroups.CreateQuery(queryText).AsPages())`
- ETag fields on the classes `IndividualEnrollment`, `EnrollmentGroup`, and `DeviceRegistrationState` are now taken as the `Azure.ETag` type instead of strings.
- Twin.Tags is now of type `IDictionary<string, object>`.
- `CustomAllocationDefinition.WebhookUri` is now of type `System.Uri` instead of `System.String`.
Expand Down Expand Up @@ -505,6 +513,10 @@ These span across all service clients.
| `X509CertificateInfo.SHA256Thumbprint` | `X509CertificateInfo.Sha256Thumbprint` | See³ |
| `ProvisioningServiceClientException` | `ProvisioningServiceException` | |
| `ProvisioningClientCapabilities.IotEdge` | `InitialClientCapabilities.IsIotEdge` | Boolean properties should start with a verb, usually "Is". |
| `Query` | Class removed | `AsyncPageable` type replaces this type and is returned by all query functions now |
| `QueryResult` | Class removed | `AsyncPageable` type replaces this type and is returned by all query functions now |
| `QueryResultType` | Class removed | The `AsyncPageable` returned by each Query API has a hardcoded type now (`IndividualEnrollment`, `EnrollmentGroup`, or `DeviceRegistrationState`) |


### Security provider client

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task ServiceClient_QueryDevicesInvalidServiceCertificateHttp_Fails(
using var sc = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionStringInvalidServiceCertificate);

// act
Func<Task> act = async () => await sc.Query.CreateAsync<ClientTwin>("select * from devices").GetAsyncEnumerator().MoveNextAsync().ConfigureAwait(false);
Func<Task> act = async () => await sc.Query.Create<ClientTwin>("select * from devices").GetAsyncEnumerator().MoveNextAsync().ConfigureAwait(false);

// assert
var error = await act.Should().ThrowAsync<IotHubServiceException>();
Expand Down
26 changes: 13 additions & 13 deletions e2e/test/iothub/service/QueryClientE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task TwinQuery_Works()

await WaitForDevicesToBeQueryableAsync(serviceClient.Query, queryText, 2).ConfigureAwait(false);

AsyncPageable<ClientTwin> queryResponse = serviceClient.Query.CreateAsync<ClientTwin>(queryText);
AsyncPageable<ClientTwin> queryResponse = serviceClient.Query.Create<ClientTwin>(queryText);
IAsyncEnumerator<ClientTwin> enumerator = queryResponse.GetAsyncEnumerator();

// assert
Expand Down Expand Up @@ -77,7 +77,7 @@ public async Task TwinQuery_CustomPaginationWorks()
await WaitForDevicesToBeQueryableAsync(serviceClient.Query, queryText, 3).ConfigureAwait(false);

AsyncPageable<ClientTwin> queryResponse = serviceClient.Query.
CreateAsync<ClientTwin>(queryText);
Create<ClientTwin>(queryText);
IAsyncEnumerator<Page<ClientTwin>> enumerator = queryResponse.AsPages(null, 1).GetAsyncEnumerator();
(await enumerator.MoveNextAsync().ConfigureAwait(false)).Should().BeTrue("Should have at least one page of jobs.");

Expand All @@ -89,7 +89,7 @@ public async Task TwinQuery_CustomPaginationWorks()

// restart the query, but with a page size of 3 this time
queryResponse = serviceClient.Query.
CreateAsync<ClientTwin>(queryText);
Create<ClientTwin>(queryText);
enumerator = queryResponse.AsPages(null, 3).GetAsyncEnumerator();

// consume the first page of results so the next MoveNextAsync gets a new page
Expand Down Expand Up @@ -132,7 +132,7 @@ public async Task TwinQuery_IterateByItemAcrossPages()
await WaitForDevicesToBeQueryableAsync(serviceClient.Query, queryText, 3).ConfigureAwait(false);

AsyncPageable<ClientTwin> twinQuery = serviceClient.Query.
CreateAsync<ClientTwin>(queryText);
Create<ClientTwin>(queryText);

// assert

Expand Down Expand Up @@ -172,7 +172,7 @@ public async Task TwinQuery_IterateByItemWorksWithinPage()
await WaitForDevicesToBeQueryableAsync(serviceClient.Query, queryText, 3).ConfigureAwait(false);

AsyncPageable<ClientTwin> twinQuery = serviceClient.Query
.CreateAsync<ClientTwin>(queryText);
.Create<ClientTwin>(queryText);

// assert

Expand Down Expand Up @@ -205,7 +205,7 @@ public async Task JobQuery_QueryWorks()
string query = "SELECT * FROM devices.jobs";
await WaitForJobToBeQueryableAsync(serviceClient.Query, query, 1).ConfigureAwait(false);

AsyncPageable<ScheduledJob> queryResponse = serviceClient.Query.CreateAsync<ScheduledJob>(query);
AsyncPageable<ScheduledJob> queryResponse = serviceClient.Query.Create<ScheduledJob>(query);
IAsyncEnumerator<ScheduledJob> enumerator = queryResponse.GetAsyncEnumerator();
(await enumerator.MoveNextAsync().ConfigureAwait(false)).Should().BeTrue("Should have at least one page of jobs.");
ScheduledJob queriedJob = enumerator.Current;
Expand Down Expand Up @@ -234,7 +234,7 @@ public async Task JobQuery_QueryByTypeWorks()
await ScheduleJobToBeQueriedAsync(serviceClient.ScheduledJobs, testDevice.Id).ConfigureAwait(false);
await WaitForJobToBeQueryableAsync(serviceClient.Query, 1, null, null).ConfigureAwait(false);

AsyncPageable<ScheduledJob> queryResponse = serviceClient.Query.CreateJobsQueryAsync();
AsyncPageable<ScheduledJob> queryResponse = serviceClient.Query.CreateJobsQuery();
IAsyncEnumerator<ScheduledJob> enumerator = queryResponse.GetAsyncEnumerator();
(await enumerator.MoveNextAsync().ConfigureAwait(false)).Should().BeTrue("Should have at least one page of jobs.");
ScheduledJob queriedJob = enumerator.Current;
Expand All @@ -258,7 +258,7 @@ public async Task RawQuery_QueryWorks()

string query = "SELECT COUNT() as TotalNumberOfDevices FROM devices";

AsyncPageable<RawQuerySerializationClass> queryResponse = serviceClient.Query.CreateAsync<RawQuerySerializationClass>(query);
AsyncPageable<RawQuerySerializationClass> queryResponse = serviceClient.Query.Create<RawQuerySerializationClass>(query);
IAsyncEnumerator<RawQuerySerializationClass> enumerator = queryResponse.GetAsyncEnumerator();
(await enumerator.MoveNextAsync().ConfigureAwait(false)).Should().BeTrue("Should have at least one page of jobs.");
RawQuerySerializationClass queriedJob = enumerator.Current;
Expand All @@ -271,12 +271,12 @@ private async Task WaitForDevicesToBeQueryableAsync(QueryClient queryClient, str
// so keep executing the query until both devices are returned in the results or until a timeout.
using var cancellationTokenSource = new CancellationTokenSource(_queryableDelayTimeout);
CancellationToken cancellationToken = cancellationTokenSource.Token;
IAsyncEnumerator<Page<ClientTwin>> enumerator = queryClient.CreateAsync<ClientTwin>(query).AsPages().GetAsyncEnumerator();
IAsyncEnumerator<Page<ClientTwin>> enumerator = queryClient.Create<ClientTwin>(query).AsPages().GetAsyncEnumerator();
await enumerator.MoveNextAsync();
while (enumerator.Current.Values.Count < expectedCount)
{
await Task.Delay(100).ConfigureAwait(false);
enumerator = queryClient.CreateAsync<ClientTwin>(query).AsPages().GetAsyncEnumerator();
enumerator = queryClient.Create<ClientTwin>(query).AsPages().GetAsyncEnumerator();
await enumerator.MoveNextAsync().ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested(); // timed out waiting for the devices to become queryable
}
Expand All @@ -292,7 +292,7 @@ private async Task WaitForJobToBeQueryableAsync(QueryClient queryClient, string
{
await Task.Delay(100).ConfigureAwait(false);
cancellationTokenSource.Token.IsCancellationRequested.Should().BeFalse("timed out waiting for the devices to become queryable");
enumerator = queryClient.CreateAsync<ScheduledJob>(query).AsPages().GetAsyncEnumerator();
enumerator = queryClient.Create<ScheduledJob>(query).AsPages().GetAsyncEnumerator();
await enumerator.MoveNextAsync().ConfigureAwait(false);
} while (enumerator.Current.Values.Count < expectedCount);
}
Expand All @@ -308,13 +308,13 @@ private async Task WaitForJobToBeQueryableAsync(QueryClient queryClient, int exp
JobType = jobType,
JobStatus = status,
};
IAsyncEnumerator<Page<ScheduledJob>> enumerator = queryClient.CreateJobsQueryAsync(options).AsPages().GetAsyncEnumerator();
IAsyncEnumerator<Page<ScheduledJob>> enumerator = queryClient.CreateJobsQuery(options).AsPages().GetAsyncEnumerator();
await enumerator.MoveNextAsync().ConfigureAwait(false);
while (enumerator.Current.Values.Count < expectedCount)
{
cancellationTokenSource.Token.IsCancellationRequested.Should().BeFalse("timed out waiting for the devices to become queryable");
await Task.Delay(100).ConfigureAwait(false);
enumerator = queryClient.CreateJobsQueryAsync(options).AsPages().GetAsyncEnumerator();
enumerator = queryClient.CreateJobsQuery(options).AsPages().GetAsyncEnumerator();
await enumerator.MoveNextAsync().ConfigureAwait(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Azure;
using FluentAssertions;
using Microsoft.Azure.Devices.E2ETests.Helpers;
using Microsoft.Azure.Devices.Provisioning.Client;
Expand Down Expand Up @@ -37,10 +38,10 @@ public async Task ProvisioningServiceClient_QueryInvalidServiceCertificateHttp_F
{
// arrange
using var provisioningServiceClient = new ProvisioningServiceClient(TestConfiguration.Provisioning.ConnectionStringInvalidServiceCertificate);
Query q = provisioningServiceClient.EnrollmentGroups.CreateQuery("SELECT * FROM enrollmentGroups");
AsyncPageable<EnrollmentGroup> q = provisioningServiceClient.EnrollmentGroups.CreateQuery("SELECT * FROM enrollmentGroups");

// act
Func<Task> act = async () => await q.NextAsync();
Func<Task> act = async () => await q.GetAsyncEnumerator().MoveNextAsync();

// assert
var error = await act.Should().ThrowAsync<ProvisioningServiceException>().ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using FluentAssertions;
using FluentAssertions.Specialized;
using Microsoft.Azure.Devices.E2ETests.Helpers;
using Microsoft.Azure.Devices.Provisioning.Service;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.Azure.Devices.E2ETests.Provisioning
{
[TestClass]
[TestCategory("E2E")]
[TestCategory("DPS")]
public class ProvisioningServiceDeviceRegistrationTests : E2EMsTestBase
{
private static readonly string s_proxyServerAddress = TestConfiguration.IotHub.ProxyServerAddress;
private static readonly string s_devicePrefix = $"{nameof(ProvisioningServiceIndividualEnrollmentTests)}_";

private static readonly ProvisioningServiceExponentialBackoffRetryPolicy s_provisioningServiceRetryPolicy = new(20, TimeSpan.FromSeconds(3), true);

[TestMethod]
[Timeout(TestTimeoutMilliseconds)]
public async Task ProvisioningServiceClient_DeviceRegistrationState_Query_Ok()
{
using var provisioningServiceClient = new ProvisioningServiceClient(TestConfiguration.Provisioning.ConnectionString);

// Create an enrollment group so that the query is guaranteed to return at least one entry
string enrollmentGroupId = Guid.NewGuid().ToString();
var enrollmentGroup = new EnrollmentGroup(enrollmentGroupId, new SymmetricKeyAttestation());
await provisioningServiceClient.EnrollmentGroups
.CreateOrUpdateAsync(enrollmentGroup).ConfigureAwait(false);

try
{
string queryString = "SELECT * FROM enrollmentGroups";
IAsyncEnumerable<DeviceRegistrationState> query = provisioningServiceClient.DeviceRegistrationStates.CreateEnrollmentGroupQuery(queryString, enrollmentGroupId);
await foreach (DeviceRegistrationState state in query)
{
state.LastUpdatedOnUtc.Should().NotBeNull();
}
}
finally
{
try
{
await provisioningServiceClient.EnrollmentGroups
.DeleteAsync(enrollmentGroupId).ConfigureAwait(false);
}
catch (Exception e)
{
// Failed to cleanup after the test, but don't fail the test because of this
VerboseTestLogger.WriteLine($"Failed to clean up enrollment group due to {e}");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,52 @@ public async Task ProvisioningServiceClient_EnrollmentGroups_SymmetricKey_BulkOp
deleteBulkEnrollmentResult.IsSuccessful.Should().BeTrue();
}

[TestMethod]
public async Task ProvisioningServiceClient_EnrollmentGroups_Query_Ok()
{
using var provisioningServiceClient = new ProvisioningServiceClient(TestConfiguration.Provisioning.ConnectionString);

// Create an enrollment group so that the query is guaranteed to return at least one entry
string enrollmentGroupId = Guid.NewGuid().ToString();
var enrollmentGroup = new EnrollmentGroup(enrollmentGroupId, new SymmetricKeyAttestation());
await provisioningServiceClient.EnrollmentGroups
.CreateOrUpdateAsync(enrollmentGroup).ConfigureAwait(false);

int maxCount = 5;
int currentCount = 0;

try
{
string queryString = "SELECT * FROM enrollmentGroups";
IAsyncEnumerable<EnrollmentGroup> query = provisioningServiceClient.EnrollmentGroups.CreateQuery(queryString);
await foreach (EnrollmentGroup enrollment in query)
{
// Just checking that the returned type was, in fact, an enrollment group and that deserialization
// of the always-present field works.
enrollment.Id.Should().NotBeNull();

// Don't want to query all the enrollment groups. Just query a few.
if (++currentCount >= maxCount)
{
return;
}
}
}
finally
{
try
{
await provisioningServiceClient.EnrollmentGroups
.DeleteAsync(enrollmentGroupId).ConfigureAwait(false);
}
catch (Exception e)
{
// Failed to cleanup after the test, but don't fail the test because of this
VerboseTestLogger.WriteLine($"Failed to clean up enrollment group due to {e}");
}
}
}

private static async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType attestationType)
{
string groupId = AttestationTypeToString(attestationType) + "-" + Guid.NewGuid();
Expand Down
Loading