// This method updates local state and should be called only after acquiring edgeHubConfigLock async Task <Option <EdgeHubConfig> > PatchDesiredProperties(TwinCollection baseline, TwinCollection patch) { LastDesiredStatus lastDesiredStatus; Option <EdgeHubConfig> edgeHubConfig; try { string desiredPropertiesJson = JsonEx.Merge(baseline, patch, true); this.lastDesiredProperties = Option.Some(new TwinCollection(desiredPropertiesJson)); var desiredPropertiesPatch = JsonConvert.DeserializeObject <EdgeHubDesiredProperties>(desiredPropertiesJson); edgeHubConfig = Option.Some(this.configParser.GetEdgeHubConfig(desiredPropertiesPatch)); lastDesiredStatus = new LastDesiredStatus(200, string.Empty); Events.PatchConfigSuccess(); } catch (Exception ex) { lastDesiredStatus = new LastDesiredStatus(400, $"Error while parsing desired properties - {ex.Message}"); edgeHubConfig = Option.None <EdgeHubConfig>(); Events.ErrorPatchingDesiredProperties(ex); } await this.UpdateReportedProperties(patch.Version, lastDesiredStatus); return(edgeHubConfig); }
async Task UpdateReportedPropertiesPatchAsync(string id, TwinInfo newTwinInfo, TwinCollection reportedProperties) { try { using (await this.twinLock.LockAsync()) { IEntityStore <string, TwinInfo> twinStore = this.TwinStore.Expect(() => new InvalidOperationException("Missing twin store")); await twinStore.PutOrUpdate( id, newTwinInfo, u => { string mergedJson = JsonEx.Merge(u.ReportedPropertiesPatch, reportedProperties, /*treatNullAsDelete*/ false); var mergedPatch = new TwinCollection(mergedJson); Events.UpdatingReportedPropertiesPatchCollection(id, mergedPatch.Version); return(new TwinInfo(u.Twin, mergedPatch)); }); } } catch (Exception e) { throw new InvalidOperationException($"Error updating twin patch for device {id}", e); } }
public async Task UpdateReportedProperties(string id, TwinCollection patch) { Preconditions.CheckNonWhiteSpace(id, nameof(id)); Preconditions.CheckNotNull(patch, nameof(patch)); Events.UpdatingReportedProperties(id); await this.twinEntityStore.PutOrUpdate( id, new TwinStoreEntity(new Twin { Properties = new TwinProperties { Reported = patch } }), twinInfo => { twinInfo.Twin .ForEach( twin => { TwinProperties twinProperties = twin.Properties ?? new TwinProperties(); TwinCollection reportedProperties = twinProperties.Reported ?? new TwinCollection(); string mergedReportedPropertiesString = JsonEx.Merge(reportedProperties, patch, /* treatNullAsDelete */ true); twinProperties.Reported = new TwinCollection(mergedReportedPropertiesString); twin.Properties = twinProperties; Events.MergedReportedProperties(id); }); return(twinInfo); }); }
async Task <(TwinCollection, TwinCollection)> TestTwinUpdate(DeviceClient deviceClient, string deviceName, RegistryManager rm, Twin twinPatch) { var receivedDesiredProperties = new TwinCollection(); Task DesiredPropertiesUpdateCallback(TwinCollection desiredproperties, object usercontext) { receivedDesiredProperties = desiredproperties; return(Task.CompletedTask); } await deviceClient.SetDesiredPropertyUpdateCallbackAsync(DesiredPropertiesUpdateCallback, null); // fetch the newly minted twin Twin originalCloudTwin = await deviceClient.GetTwinAsync(); Twin rmTwin = await rm.GetTwinAsync(deviceName); // updated twin in the cloud with the patch await rm.UpdateTwinAsync(deviceName, twinPatch, rmTwin.ETag); // Get the updated twin Twin updatedCloudTwin = await deviceClient.GetTwinAsync(); // replicate the patch operation locally await Task.Delay(TimeSpan.FromSeconds(5)); string mergedJson = JsonEx.Merge(originalCloudTwin.Properties.Desired, receivedDesiredProperties, true); var localMergedTwinProperties = new TwinCollection(mergedJson); return(localMergedTwinProperties, updatedCloudTwin.Properties.Desired); }
async Task UpdateDesiredPropertiesWhenTwinStoreHasTwinAsync(string id, TwinCollection desired) { bool getTwin = false; IMessage message = this.twinCollectionConverter.ToMessage(desired); using (await this.twinLock.LockAsync()) { IEntityStore <string, TwinInfo> twinStore = this.TwinStore.Expect(() => new InvalidOperationException("Missing twin store")); await twinStore.Update( id, u => { // Save the patch only if it is the next one that can be applied if (desired.Version == u.Twin.Properties.Desired.Version + 1) { Events.InOrderDesiredPropertyPatchReceived( id, u.Twin.Properties.Desired.Version, desired.Version); string mergedJson = JsonEx.Merge(u.Twin.Properties.Desired, desired, /*treatNullAsDelete*/ true); u.Twin.Properties.Desired = new TwinCollection(mergedJson); } else { Events.OutOfOrderDesiredPropertyPatchReceived( id, u.Twin.Properties.Desired.Version, desired.Version); getTwin = true; } return(new TwinInfo(u.Twin, u.ReportedPropertiesPatch)); }); } // Refresh local copy of the twin since we received an out-of-order patch if (getTwin) { Option <ICloudProxy> cloudProxy = await this.connectionManager.GetCloudConnection(id); await cloudProxy.ForEachAsync(cp => this.GetTwinInfoWhenCloudOnlineAsync(id, cp, true /* send update to device */)); } else { await this.SendDesiredPropertiesToDeviceProxy(id, message); } }
// This method updates local state and should be called only after acquiring twinLock async Task ApplyPatchAsync(TwinCollection patch) { try { string mergedJson = JsonEx.Merge(this.desiredProperties, patch, true); this.desiredProperties = new TwinCollection(mergedJson); await this.UpdateDeploymentConfig(); Events.DesiredPropertiesPatchApplied(); } catch (Exception ex) when(!ex.IsFatal()) { this.deploymentConfigInfo = Option.Some(new DeploymentConfigInfo(this.desiredProperties?.Version ?? 0, ex)); Events.DesiredPropertiesPatchFailed(ex); // Update reported properties with last desired status } }
// This method updates local state and should be called only after acquiring edgeHubConfigLock async Task <Option <EdgeHubConfig> > PatchDesiredProperties(TwinCollection baseline, TwinCollection patch) { LastDesiredStatus lastDesiredStatus; Option <EdgeHubConfig> edgeHubConfig; try { string desiredPropertiesJson = JsonEx.Merge(baseline, patch, true); var desiredProperties = new TwinCollection(desiredPropertiesJson); Events.LogDesiredPropertiesAfterPatch(desiredProperties); if (!this.CheckIfManifestSigningIsEnabled(desiredProperties)) { Events.ManifestSigningIsNotEnabled(); } else { Events.ManifestSigningIsEnabled(); if (this.ExtractHubTwinAndVerify(desiredProperties)) { Events.VerifyTwinSignatureSuceeded(); } else { Events.VerifyTwinSignatureFailed(); lastDesiredStatus = new LastDesiredStatus(400, "Twin Signature Verification failed"); return(Option.None <EdgeHubConfig>()); } } this.lastDesiredProperties = Option.Some(new TwinCollection(desiredPropertiesJson)); edgeHubConfig = Option.Some(this.configParser.GetEdgeHubConfig(desiredPropertiesJson)); lastDesiredStatus = new LastDesiredStatus(200, string.Empty); Events.PatchConfigSuccess(); } catch (Exception ex) { lastDesiredStatus = new LastDesiredStatus(400, $"Error while parsing desired properties - {ex.Message}"); edgeHubConfig = Option.None <EdgeHubConfig>(); Events.ErrorPatchingDesiredProperties(ex); } await this.UpdateReportedProperties(patch.Version, lastDesiredStatus); return(edgeHubConfig); }
async Task UpdateReportedPropertiesWhenTwinStoreHasTwinAsync(string id, TwinCollection reported, bool cloudVerified) { using (await this.twinLock.LockAsync()) { IEntityStore <string, TwinInfo> twinStore = this.TwinStore.Expect(() => new InvalidOperationException("Missing twin store")); await twinStore.Update( id, u => { if (u.Twin == null) { if (!cloudVerified) { ValidateTwinCollectionSize(reported); } var twinProperties = new TwinProperties { Desired = new TwinCollection(), Reported = reported }; var twin = new Twin(twinProperties); Events.UpdatedCachedReportedProperties(id, reported.Version, cloudVerified); return(new TwinInfo(twin, reported)); } else { string mergedJson = JsonEx.Merge(u.Twin.Properties.Reported, reported, /*treatNullAsDelete*/ true); var mergedReportedProperties = new TwinCollection(mergedJson); if (!cloudVerified) { ValidateTwinCollectionSize(mergedReportedProperties); } u.Twin.Properties.Reported = mergedReportedProperties; Events.UpdatedCachedReportedProperties(id, mergedReportedProperties.Version, cloudVerified); return(u); } }); } }
public async Task UpdateDesiredProperties(string id, TwinCollection patch) { Events.UpdatingDesiredProperties(id); Preconditions.CheckNotNull(patch, nameof(patch)); Option <Twin> storedTwin = await this.Get(id); if (storedTwin.HasValue) { await this.twinEntityStore.Update( id, twinInfo => { twinInfo.Twin .ForEach( twin => { TwinProperties twinProperties = twin.Properties ?? new TwinProperties(); TwinCollection desiredProperties = twinProperties.Desired ?? new TwinCollection(); if (desiredProperties.Version + 1 == patch.Version) { string mergedDesiredPropertiesString = JsonEx.Merge(desiredProperties, patch, /* treatNullAsDelete */ true); twinProperties.Desired = new TwinCollection(mergedDesiredPropertiesString); twin.Properties = twinProperties; Events.MergedDesiredProperties(id); } else { Events.DesiredPropertiesVersionMismatch(id, desiredProperties.Version, patch.Version); } }); return(twinInfo); }); } else { Events.NoTwinForDesiredPropertiesPatch(id); } }
public async Task Update(string id, TwinCollection patch) { using (await this.lockProvider.GetLock(id).LockAsync()) { Events.StoringReportedPropertiesInStore(id, patch); await this.twinStore.PutOrUpdate( id, new TwinStoreEntity(patch), twinInfo => { Events.UpdatingReportedPropertiesInStore(id, patch); TwinCollection updatedReportedProperties = twinInfo.ReportedPropertiesPatch .Map(reportedProperties => new TwinCollection(JsonEx.Merge(reportedProperties, patch, /*treatNullAsDelete*/ false))) .GetOrElse(() => patch); return(new TwinStoreEntity(twinInfo.Twin, Option.Maybe(updatedReportedProperties))); }); } }
public void TestMergeAllCases() { // Arrange var baseline = new { name = new { level0 = "nochange", level1 = "value1", level2 = new { level3 = "value3" }, level6 = (Dictionary <string, string>)null, }, overwrite = new { level1 = "value1" }, create = "yes", array = new object[] { new object[] { 100L, false } }, arrayString = new string[] { "a" } }; var patch = new { name = new { level0 = "nochange", // unchanged level1 = (Dictionary <string, string>)null, // existing in base. remove property, only if treatNullAsDelete = true level2 = new { level3 = "newvalue3" // existing in base, update property }, level4 = "value4", // non existant in base, add new property level5 = (Dictionary <string, string>)null // ignore, unless treatNullAsDelete = false }, overwrite = "yes", // overwrite object with value create = new // overwrite value with object { level1 = "value1", }, array = new object[] { new object[] { 100L, true }, new { a = 0, b = "doom" } }, arrayString = "a" }; var removeAll = new { name = (Dictionary <string, string>)null, overwrite = (Dictionary <string, string>)null, create = (Dictionary <string, string>)null, array = (int[])null, arrayString = (string[])null }; var removeAllInefficient = new { name = new { level0 = (Dictionary <string, string>)null, level1 = (Dictionary <string, string>)null, level2 = new { level3 = (Dictionary <string, string>)null, }, level6 = (Dictionary <string, string>)null, }, overwrite = new { level1 = (Dictionary <string, string>)null, }, create = (Dictionary <string, string>)null, array = (int[])null, arrayString = (string[])null }; var mergedExcludeNull = new { name = new { level0 = "nochange", level2 = new { level3 = "newvalue3" }, level4 = "value4", level6 = (Dictionary <string, string>)null }, overwrite = "yes", create = new { level1 = "value1", }, array = new object[] { new object[] { 100L, true }, new { a = 0, b = "doom" } }, arrayString = "a" }; var mergedIncludeNull = new { name = new { level0 = "nochange", level1 = (Dictionary <string, string>)null, level2 = new { level3 = "newvalue3" }, level4 = "value4", level5 = (Dictionary <string, string>)null, level6 = (Dictionary <string, string>)null }, overwrite = "yes", create = new { level1 = "value1", }, array = new object[] { new object[] { 100L, true }, new { a = 0, b = "doom" } }, arrayString = "a" }; var emptyBaseline = new { }; var nestedEmptyBaseline = new { name = new { level2 = new { }, }, overwrite = new { } }; var emptyPatch = new { }; // Act JToken resultCollection = JsonEx.Merge(JToken.FromObject(baseline), JToken.FromObject(patch), true); // Assert Assert.True(JToken.DeepEquals(JToken.FromObject(resultCollection), JToken.FromObject(mergedExcludeNull)), resultCollection.ToString()); // Act resultCollection = JsonEx.Merge(JToken.FromObject(baseline), JToken.FromObject(patch), false); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(mergedIncludeNull))); // Act resultCollection = JsonEx.Merge(JToken.FromObject(emptyBaseline), JToken.FromObject(emptyPatch), true); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(emptyBaseline))); // Act resultCollection = JsonEx.Merge(JToken.FromObject(baseline), JToken.FromObject(emptyPatch), true); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(baseline))); // Act resultCollection = JsonEx.Merge(JToken.FromObject(emptyBaseline), JToken.FromObject(patch), true); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(patch))); // Act resultCollection = JsonEx.Merge(JToken.FromObject(baseline), JToken.FromObject(removeAll), true); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(emptyBaseline))); // Act resultCollection = JsonEx.Merge(JToken.FromObject(baseline), JToken.FromObject(removeAllInefficient), true); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(nestedEmptyBaseline))); }