예제 #1
0
        // 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);
        }
예제 #2
0
        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);
            }
        }
예제 #3
0
 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);
     });
 }
예제 #4
0
        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);
        }
예제 #5
0
        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);
            }
        }
예제 #6
0
        // 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
            }
        }
예제 #7
0
        // 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);
        }
예제 #8
0
        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);
                    }
                });
            }
        }
예제 #9
0
        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);
            }
        }
예제 #10
0
 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)));
         });
     }
 }
예제 #11
0
        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)));
        }