Merge pull request #160 from Azure/feature/edge
Merge Edge Feature Branch into Master
This commit is contained in:
Коммит
ac04645ed1
|
@ -134,7 +134,11 @@ namespace Services.Test
|
|||
"UpdateRules",
|
||||
"DeleteRules",
|
||||
"CreateJobs",
|
||||
"UpdateSimManagement"
|
||||
"UpdateSimManagement",
|
||||
"CreateDeployments",
|
||||
"DeleteDeployments",
|
||||
"CreatePackages",
|
||||
"DeletePackages"
|
||||
};
|
||||
|
||||
return new Policy()
|
||||
|
@ -157,7 +161,11 @@ namespace Services.Test
|
|||
"CreateRules",
|
||||
"UpdateRules",
|
||||
"CreateJobs",
|
||||
"UpdateSimManagement"
|
||||
"UpdateSimManagement",
|
||||
"CreateDeployments",
|
||||
"DeleteDeployments",
|
||||
"CreatePackages",
|
||||
"DeletePackages"
|
||||
};
|
||||
|
||||
return new Policy()
|
||||
|
|
|
@ -16,7 +16,11 @@
|
|||
"UpdateRules",
|
||||
"DeleteRules",
|
||||
"CreateJobs",
|
||||
"UpdateSimManagement"
|
||||
"UpdateSimManagement",
|
||||
"CreateDeployments",
|
||||
"DeleteDeployments",
|
||||
"CreatePackages",
|
||||
"DeletePackages"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -26,7 +26,11 @@ Role Policy Example:
|
|||
"UpdateRules",
|
||||
"DeleteRules",
|
||||
"CreateJobs",
|
||||
"UpdateSimManagement"
|
||||
"UpdateSimManagement",
|
||||
"CreateDeployment",
|
||||
"DeleteDeployment",
|
||||
"CreatePackage",
|
||||
"DeletePackage"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -24,6 +24,8 @@ More information [here][rm-arch-url].
|
|||
* Get or upload logo
|
||||
* Get or set overall solution settings
|
||||
* Get or set individual user settings
|
||||
* Create or delete a package
|
||||
* Get all or a single uploaded package.
|
||||
|
||||
## Documentation
|
||||
* View the API documentation in the [Wiki](https://github.com/Azure/pcs-config-dotnet/wiki)
|
||||
|
|
|
@ -11,6 +11,7 @@ using Microsoft.Azure.IoTSolutions.UIConfig.Services.Models;
|
|||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Runtime;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Services.Test.helpers;
|
||||
using Xunit;
|
||||
|
||||
|
@ -22,6 +23,70 @@ namespace Services.Test
|
|||
private readonly Mock<IStorageAdapterClient> mockClient;
|
||||
private readonly Storage storage;
|
||||
private readonly Random rand;
|
||||
private const string PACKAGES_COLLECTION_ID = "packages";
|
||||
private const string TEST_PACKAGE_JSON =
|
||||
@"{
|
||||
""id"": ""tempid"",
|
||||
""schemaVersion"": ""1.0"",
|
||||
""content"": {
|
||||
""modulesContent"": {
|
||||
""$edgeAgent"": {
|
||||
""properties.desired"": {
|
||||
""schemaVersion"": ""1.0"",
|
||||
""runtime"": {
|
||||
""type"": ""docker"",
|
||||
""settings"": {
|
||||
""loggingOptions"": """",
|
||||
""minDockerVersion"": ""v1.25""
|
||||
}
|
||||
},
|
||||
""systemModules"": {
|
||||
""edgeAgent"": {
|
||||
""type"": ""docker"",
|
||||
""settings"": {
|
||||
""image"": ""mcr.microsoft.com/azureiotedge-agent:1.0"",
|
||||
""createOptions"": ""{}""
|
||||
}
|
||||
},
|
||||
""edgeHub"": {
|
||||
""type"": ""docker"",
|
||||
""settings"": {
|
||||
""image"": ""mcr.microsoft.com/azureiotedge-hub:1.0"",
|
||||
""createOptions"": ""{}""
|
||||
},
|
||||
""status"": ""running"",
|
||||
""restartPolicy"": ""always""
|
||||
}
|
||||
},
|
||||
""modules"": {}
|
||||
}
|
||||
},
|
||||
""$edgeHub"": {
|
||||
""properties.desired"": {
|
||||
""schemaVersion"": ""1.0"",
|
||||
""routes"": {
|
||||
""route"": ""FROM /messages/* INTO $upstream""
|
||||
},
|
||||
""storeAndForwardConfiguration"": {
|
||||
""timeToLiveSecs"": 7200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
""targetCondition"": ""*"",
|
||||
""priority"": 30,
|
||||
""labels"": {
|
||||
""Name"": ""Test""
|
||||
},
|
||||
""createdTimeUtc"": ""2018-08-20T18:05:55.482Z"",
|
||||
""lastUpdatedTimeUtc"": ""2018-08-20T18:05:55.482Z"",
|
||||
""etag"": null,
|
||||
""metrics"": {
|
||||
""results"": {},
|
||||
""queries"": {}
|
||||
}
|
||||
}";
|
||||
|
||||
public StorageTest()
|
||||
{
|
||||
|
@ -599,5 +664,98 @@ namespace Services.Test
|
|||
return result;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddPackageTest()
|
||||
{
|
||||
// Arrange
|
||||
const string collectionId = "packages";
|
||||
const string key = "package name";
|
||||
var pkg = new Package
|
||||
{
|
||||
Id = string.Empty,
|
||||
Name = key,
|
||||
Type = PackageType.EdgeManifest,
|
||||
Content = TEST_PACKAGE_JSON
|
||||
};
|
||||
var value = JsonConvert.SerializeObject(pkg);
|
||||
|
||||
this.mockClient
|
||||
.Setup(x => x.CreateAsync(
|
||||
It.Is<string>(i => i == collectionId),
|
||||
It.Is<string>(i => this.IsMatchingPackage(i, value))))
|
||||
.ReturnsAsync(new ValueApiModel
|
||||
{
|
||||
Key = key,
|
||||
Data = value
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await this.storage.AddPackageAsync(pkg);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(pkg.Name, result.Name);
|
||||
Assert.Equal(pkg.Type, result.Type);
|
||||
Assert.Equal(pkg.Content, result.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvalidPackageThrowsTest()
|
||||
{
|
||||
// Arrange
|
||||
var pkg = new Package
|
||||
{
|
||||
Id = string.Empty,
|
||||
Name = "testpackage",
|
||||
Type = PackageType.EdgeManifest,
|
||||
Content = "InvalidPackage"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidInputException>(async () =>
|
||||
await this.storage.AddPackageAsync(pkg));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeletePackageAsyncTest()
|
||||
{
|
||||
// Arrange
|
||||
var packageId = this.rand.NextString();
|
||||
|
||||
this.mockClient
|
||||
.Setup(x => x.DeleteAsync(It.Is<string>(s => s == PACKAGES_COLLECTION_ID),
|
||||
It.Is<string>(s => s == packageId)))
|
||||
.Returns(Task.FromResult(0));
|
||||
|
||||
// Act
|
||||
await this.storage.DeletePackageAsync(packageId);
|
||||
|
||||
// Assert
|
||||
this.mockClient
|
||||
.Verify(x => x.DeleteAsync(
|
||||
It.Is<string>(s => s == PACKAGES_COLLECTION_ID),
|
||||
It.Is<string>(s => s == packageId)),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
private bool IsMatchingPackage(string pkgJson, string originalPkgJson)
|
||||
{
|
||||
const string dateCreatedField = "DateCreated";
|
||||
var createdPkg = JObject.Parse(pkgJson);
|
||||
var originalPkg = JObject.Parse(originalPkgJson);
|
||||
|
||||
// To prevent false failures on unit tests we allow a couple of seconds diffence
|
||||
// when verifying the date created.
|
||||
var dateCreated = DateTimeOffset.Parse(createdPkg[dateCreatedField].ToString());
|
||||
var secondsDiff = (DateTimeOffset.UtcNow - dateCreated).TotalSeconds;
|
||||
if (secondsDiff > 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
createdPkg.Remove(dateCreatedField);
|
||||
originalPkg.Remove(dateCreatedField);
|
||||
|
||||
return JToken.DeepEquals(createdPkg, originalPkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.UIConfig.Services.Models
|
||||
{
|
||||
public class Package
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PackageType Type { get; set; }
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public string DateCreated { get; set; }
|
||||
}
|
||||
|
||||
public enum PackageType
|
||||
{
|
||||
EdgeManifest
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Devices" Version="1.17.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="2.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.External;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Models;
|
||||
|
@ -25,6 +27,10 @@ namespace Microsoft.Azure.IoTSolutions.UIConfig.Services
|
|||
Task<DeviceGroup> CreateDeviceGroupAsync(DeviceGroup input);
|
||||
Task<DeviceGroup> UpdateDeviceGroupAsync(string id, DeviceGroup input, string etag);
|
||||
Task DeleteDeviceGroupAsync(string id);
|
||||
Task<IEnumerable<Package>> GetPackagesAsync();
|
||||
Task<Package> GetPackageAsync(string id);
|
||||
Task<Package> AddPackageAsync(Package package);
|
||||
Task DeletePackageAsync(string id);
|
||||
}
|
||||
|
||||
public class Storage : IStorage
|
||||
|
@ -37,7 +43,9 @@ namespace Microsoft.Azure.IoTSolutions.UIConfig.Services
|
|||
internal const string LOGO_KEY = "logo";
|
||||
internal const string USER_COLLECTION_ID = "user-settings";
|
||||
internal const string DEVICE_GROUP_COLLECTION_ID = "devicegroups";
|
||||
internal const string PACKAGES_COLLECTION_ID = "packages";
|
||||
private const string AZURE_MAPS_KEY = "AzureMapsKey";
|
||||
private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:sszzz";
|
||||
|
||||
public Storage(
|
||||
IStorageAdapterClient client,
|
||||
|
@ -169,6 +177,45 @@ namespace Microsoft.Azure.IoTSolutions.UIConfig.Services
|
|||
await this.client.DeleteAsync(DEVICE_GROUP_COLLECTION_ID, id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Package>> GetPackagesAsync()
|
||||
{
|
||||
var response = await this.client.GetAllAsync(PACKAGES_COLLECTION_ID);
|
||||
return response.Items.AsParallel().Select(this.CreatePackageServiceModel);
|
||||
}
|
||||
|
||||
public async Task<Package> AddPackageAsync(Package package)
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonConvert.DeserializeObject<Configuration>(package.Content);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new InvalidInputException("Package provided is not a valid deployment manifest");
|
||||
}
|
||||
|
||||
package.DateCreated = DateTimeOffset.UtcNow.ToString(DATE_FORMAT);
|
||||
var value = JsonConvert.SerializeObject(package,
|
||||
Formatting.Indented,
|
||||
new JsonSerializerSettings {
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
|
||||
var response = await this.client.CreateAsync(PACKAGES_COLLECTION_ID, value);
|
||||
return this.CreatePackageServiceModel(response);
|
||||
}
|
||||
|
||||
public async Task DeletePackageAsync(string id)
|
||||
{
|
||||
await this.client.DeleteAsync(PACKAGES_COLLECTION_ID, id);
|
||||
}
|
||||
|
||||
public async Task<Package> GetPackageAsync(string id)
|
||||
{
|
||||
var response = await this.client.GetAsync(PACKAGES_COLLECTION_ID, id);
|
||||
return this.CreatePackageServiceModel(response);
|
||||
}
|
||||
|
||||
private DeviceGroup CreateGroupServiceModel(ValueApiModel input)
|
||||
{
|
||||
var output = JsonConvert.DeserializeObject<DeviceGroup>(input.Data);
|
||||
|
@ -176,5 +223,12 @@ namespace Microsoft.Azure.IoTSolutions.UIConfig.Services
|
|||
output.ETag = input.ETag;
|
||||
return output;
|
||||
}
|
||||
|
||||
private Package CreatePackageServiceModel(ValueApiModel input)
|
||||
{
|
||||
var output = JsonConvert.DeserializeObject<Package>(input.Data);
|
||||
output.Id = input.Key;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.WebService.v1.Controllers;
|
||||
using Moq;
|
||||
using WebService.Test.helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace WebService.Test.Controllers
|
||||
{
|
||||
public class PackageControllerTest
|
||||
{
|
||||
private readonly Mock<IStorage> mockStorage;
|
||||
private readonly PackageController controller;
|
||||
private readonly Random rand;
|
||||
private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:sszzz";
|
||||
|
||||
public PackageControllerTest()
|
||||
{
|
||||
this.mockStorage = new Mock<IStorage>();
|
||||
this.controller = new PackageController(this.mockStorage.Object);
|
||||
this.rand = new Random();
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("EdgeManifest", "filename", true, false)]
|
||||
[InlineData("EdgeManifest", "filename", false, true)]
|
||||
[InlineData("EdgeManifest", "", true, true)]
|
||||
[InlineData("BAD_TYPE", "filename", true, true)]
|
||||
public async Task PostAsyncExceptionVerificationTest(string type, string filename,
|
||||
bool isValidFileProvided, bool expectException)
|
||||
{
|
||||
// Arrange
|
||||
IFormFile file = null;
|
||||
if (isValidFileProvided)
|
||||
{
|
||||
file = this.CreateSampleFile(filename);
|
||||
}
|
||||
|
||||
this.mockStorage.Setup(x => x.AddPackageAsync(
|
||||
It.Is<Package>(p => p.Type.ToString().Equals(type) &&
|
||||
p.Name.Equals(filename))))
|
||||
.ReturnsAsync(new Package() {
|
||||
Name = filename,
|
||||
Type = PackageType.EdgeManifest
|
||||
});
|
||||
try
|
||||
{
|
||||
// Act
|
||||
var package = await this.controller.PostAsync(type, file);
|
||||
|
||||
// Assert
|
||||
Assert.False(expectException);
|
||||
Assert.Equal(filename, package.Name);
|
||||
Assert.Equal(type, package.Type.ToString());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Assert.True(expectException);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPackageTest()
|
||||
{
|
||||
// Arrange
|
||||
const string id = "packageId";
|
||||
const string name = "packageName";
|
||||
const PackageType type = PackageType.EdgeManifest;
|
||||
const string content = "{}";
|
||||
string dateCreated = DateTime.UtcNow.ToString(DATE_FORMAT);
|
||||
|
||||
this.mockStorage
|
||||
.Setup(x => x.GetPackageAsync(id))
|
||||
.ReturnsAsync(new Package()
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Content = content,
|
||||
Type = type,
|
||||
DateCreated = dateCreated
|
||||
});
|
||||
|
||||
// Act
|
||||
var pkg = await this.controller.GetAsync(id);
|
||||
|
||||
// Assert
|
||||
this.mockStorage
|
||||
.Verify(x => x.GetPackageAsync(id), Times.Once);
|
||||
|
||||
Assert.Equal(id, pkg.Id);
|
||||
Assert.Equal(name, pkg.Name);
|
||||
Assert.Equal(type, pkg.Type);
|
||||
Assert.Equal(content, pkg.Content);
|
||||
Assert.Equal(dateCreated, pkg.DateCreated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllPackageTest()
|
||||
{
|
||||
// Arrange
|
||||
const string id = "packageId";
|
||||
const string name = "packageName";
|
||||
const PackageType type = PackageType.EdgeManifest;
|
||||
const string content = "{}";
|
||||
string dateCreated = DateTime.UtcNow.ToString(DATE_FORMAT);
|
||||
|
||||
int[] idx = new int[] {0, 1, 2};
|
||||
var packages = idx.Select(i => new Package()
|
||||
{
|
||||
Id = id + i,
|
||||
Name = name + i,
|
||||
Content = content + i,
|
||||
Type = type + i,
|
||||
DateCreated = dateCreated
|
||||
}).ToList();
|
||||
|
||||
this.mockStorage
|
||||
.Setup(x => x.GetPackagesAsync())
|
||||
.ReturnsAsync(packages);
|
||||
|
||||
// Act
|
||||
var resultPackages = await this.controller.GetAllAsync();
|
||||
|
||||
// Assert
|
||||
this.mockStorage
|
||||
.Verify(x => x.GetPackagesAsync(), Times.Once);
|
||||
|
||||
foreach (int i in idx)
|
||||
{
|
||||
var pkg = resultPackages.Items.ElementAt(i);
|
||||
Assert.Equal(id + i, pkg.Id);
|
||||
Assert.Equal(name + i, pkg.Name);
|
||||
Assert.Equal(type + i, pkg.Type);
|
||||
Assert.Equal(content + i, pkg.Content);
|
||||
Assert.Equal(dateCreated, pkg.DateCreated);
|
||||
}
|
||||
}
|
||||
|
||||
private FormFile CreateSampleFile(string filename)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
stream.WriteByte(100);
|
||||
stream.Flush();
|
||||
stream.Position = 0;
|
||||
return new FormFile(stream, 0, 1, "file", filename);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.WebService.v1.Filters;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.WebService.v1.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.UIConfig.WebService.v1.Controllers
|
||||
{
|
||||
[Route(Version.PATH + "/packages"), TypeFilter(typeof(ExceptionsFilterAttribute))]
|
||||
public class PackageController : Controller
|
||||
{
|
||||
private readonly IStorage storage;
|
||||
|
||||
public PackageController(IStorage storage)
|
||||
{
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<PackageListApiModel> GetAllAsync()
|
||||
{
|
||||
return new PackageListApiModel(await this.storage.GetPackagesAsync());
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<PackageApiModel> GetAsync(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new InvalidInputException("Valid id must be provided");
|
||||
}
|
||||
|
||||
return new PackageApiModel(await this.storage.GetPackageAsync(id));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("CreatePackages")]
|
||||
public async Task<PackageApiModel> PostAsync(string type, IFormFile package)
|
||||
{
|
||||
if (string.IsNullOrEmpty(type))
|
||||
{
|
||||
throw new InvalidInputException("Package type must be provided");
|
||||
}
|
||||
|
||||
bool isValidPackageType = Enum.TryParse(type, true, out PackageType uploadedPackageType);
|
||||
if (!isValidPackageType)
|
||||
{
|
||||
throw new InvalidInputException($"Provided package type {type} is not valid.");
|
||||
}
|
||||
|
||||
if (package == null || package.Length == 0 || string.IsNullOrEmpty(package.FileName))
|
||||
{
|
||||
throw new InvalidInputException("Package uploaded is missing or invalid.");
|
||||
}
|
||||
|
||||
string packageContent;
|
||||
using (var streamReader = new StreamReader(package.OpenReadStream()))
|
||||
{
|
||||
packageContent = await streamReader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
var packageToAdd = new Package()
|
||||
{
|
||||
Content = packageContent,
|
||||
Name = package.FileName,
|
||||
Type = uploadedPackageType
|
||||
};
|
||||
|
||||
return new PackageApiModel(await this.storage.AddPackageAsync(packageToAdd));
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize("DeletePackages")]
|
||||
public async Task DeleteAsync(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new InvalidInputException("Valid id must be provided");
|
||||
}
|
||||
|
||||
await this.storage.DeletePackageAsync(id);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.UIConfig.WebService.v1.Models
|
||||
{
|
||||
public class PackageApiModel
|
||||
{
|
||||
[JsonProperty("Id")]
|
||||
public string Id;
|
||||
|
||||
[JsonProperty("Name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("Type")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PackageType Type { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DateCreated")]
|
||||
public string DateCreated { get; set; }
|
||||
|
||||
[JsonProperty("Content")]
|
||||
public string Content { get; set; }
|
||||
|
||||
public PackageApiModel(Package model)
|
||||
{
|
||||
this.Id = model.Id;
|
||||
this.Name = model.Name;
|
||||
this.Type = model.Type;
|
||||
this.DateCreated = model.DateCreated;
|
||||
this.Content = model.Content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.IoTSolutions.UIConfig.Services.Models;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.UIConfig.WebService.v1.Models
|
||||
{
|
||||
public class PackageListApiModel
|
||||
{
|
||||
public IEnumerable<PackageApiModel> Items { get; set; }
|
||||
|
||||
public PackageListApiModel(IEnumerable<Package> models)
|
||||
{
|
||||
this.Items = models.Select(m => new PackageApiModel(m));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
using Services.Test.helpers;
|
||||
using Microsoft.Azure.Devices;
|
||||
|
||||
namespace Services.Test
|
||||
{
|
||||
public class DeploymentsTest
|
||||
{
|
||||
private readonly Deployments deployments;
|
||||
private readonly Mock<RegistryManager> registry;
|
||||
|
||||
private const string DEPLOYMENT_NAME_LABEL = "Name";
|
||||
private const string DEPLOYMENT_GROUP_ID_LABEL = "DeviceGroupId";
|
||||
private const string DEPLOYMENT_GROUP_NAME_LABEL = "DeviceGroupName";
|
||||
private const string DEPLOYMENT_PACKAGE_NAME_LABEL = "PackageName";
|
||||
private const string RM_CREATED_LABEL = "RMDeployment";
|
||||
private const string RESOURCE_NOT_FOUND_EXCEPTION =
|
||||
"Microsoft.Azure.IoTSolutions.IotHubManager.Services." +
|
||||
"Exceptions.ResourceNotSupportedException, Microsoft.Azure." +
|
||||
"IoTSolutions.IotHubManager.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
|
||||
|
||||
private const string TEST_PACKAGE_JSON =
|
||||
@"{
|
||||
""id"": ""tempid"",
|
||||
""schemaVersion"": ""1.0"",
|
||||
""content"": {
|
||||
""modulesContent"": {
|
||||
""$edgeAgent"": {
|
||||
""properties.desired"": {
|
||||
""schemaVersion"": ""1.0"",
|
||||
""runtime"": {
|
||||
""type"": ""docker"",
|
||||
""settings"": {
|
||||
""loggingOptions"": """",
|
||||
""minDockerVersion"": ""v1.25""
|
||||
}
|
||||
},
|
||||
""systemModules"": {
|
||||
""edgeAgent"": {
|
||||
""type"": ""docker"",
|
||||
""settings"": {
|
||||
""image"": ""mcr.microsoft.com/azureiotedge-agent:1.0"",
|
||||
""createOptions"": ""{}""
|
||||
}
|
||||
},
|
||||
""edgeHub"": {
|
||||
""type"": ""docker"",
|
||||
""settings"": {
|
||||
""image"": ""mcr.microsoft.com/azureiotedge-hub:1.0"",
|
||||
""createOptions"": ""{}""
|
||||
},
|
||||
""status"": ""running"",
|
||||
""restartPolicy"": ""always""
|
||||
}
|
||||
},
|
||||
""modules"": {}
|
||||
}
|
||||
},
|
||||
""$edgeHub"": {
|
||||
""properties.desired"": {
|
||||
""schemaVersion"": ""1.0"",
|
||||
""routes"": {
|
||||
""route"": ""FROM /messages/* INTO $upstream""
|
||||
},
|
||||
""storeAndForwardConfiguration"": {
|
||||
""timeToLiveSecs"": 7200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
""targetCondition"": ""*"",
|
||||
""priority"": 30,
|
||||
""labels"": {
|
||||
""Name"": ""Test""
|
||||
},
|
||||
""createdTimeUtc"": ""2018-08-20T18:05:55.482Z"",
|
||||
""lastUpdatedTimeUtc"": ""2018-08-20T18:05:55.482Z"",
|
||||
""etag"": null,
|
||||
""metrics"": {
|
||||
""results"": {},
|
||||
""queries"": {}
|
||||
}
|
||||
}";
|
||||
|
||||
public DeploymentsTest()
|
||||
{
|
||||
this.registry = new Mock<RegistryManager>();
|
||||
this.deployments = new Deployments(this.registry.Object,
|
||||
"mockIoTHub");
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("depname", "dvcgroupid", "dvcquery", TEST_PACKAGE_JSON, 10, "")]
|
||||
[InlineData("", "dvcgroupid", "dvcquery", TEST_PACKAGE_JSON, 10, "System.ArgumentNullException")]
|
||||
[InlineData("depname", "", "dvcquery", TEST_PACKAGE_JSON, 10, "System.ArgumentNullException")]
|
||||
[InlineData("depname", "dvcgroupid", "", TEST_PACKAGE_JSON, 10, "System.ArgumentNullException")]
|
||||
[InlineData("depname", "dvcgroupid", "dvcquery", "", 10, "System.ArgumentNullException")]
|
||||
[InlineData("depname", "dvcgroupid", "dvcquery", TEST_PACKAGE_JSON, -1, "System.ArgumentOutOfRangeException")]
|
||||
public async Task CreateDeploymentTest(string deploymentName, string deviceGroupId,
|
||||
string deviceGroupQuery, string packageContent,
|
||||
int priority, string expectedException)
|
||||
{
|
||||
// Arrange
|
||||
var depModel = new DeploymentServiceModel()
|
||||
{
|
||||
Name = deploymentName,
|
||||
DeviceGroupId = deviceGroupId,
|
||||
DeviceGroupQuery = deviceGroupQuery,
|
||||
PackageContent = packageContent,
|
||||
Priority = priority
|
||||
};
|
||||
|
||||
var newConfig = new Configuration("test-config")
|
||||
{
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ DEPLOYMENT_NAME_LABEL, deploymentName },
|
||||
{ DEPLOYMENT_GROUP_ID_LABEL, deviceGroupId },
|
||||
{ RM_CREATED_LABEL, bool.TrueString },
|
||||
}, Priority = priority
|
||||
};
|
||||
|
||||
this.registry.Setup(r => r.AddConfigurationAsync(It.Is<Configuration>(c =>
|
||||
c.Labels.ContainsKey(DEPLOYMENT_NAME_LABEL) &&
|
||||
c.Labels.ContainsKey(DEPLOYMENT_GROUP_ID_LABEL) &&
|
||||
c.Labels.ContainsKey(RM_CREATED_LABEL) &&
|
||||
c.Labels[DEPLOYMENT_NAME_LABEL] == deploymentName &&
|
||||
c.Labels[DEPLOYMENT_GROUP_ID_LABEL] == deviceGroupId &&
|
||||
c.Labels[RM_CREATED_LABEL] == bool.TrueString)))
|
||||
.ReturnsAsync(newConfig);
|
||||
|
||||
// Act
|
||||
if (string.IsNullOrEmpty(expectedException))
|
||||
{
|
||||
var createdDeployment = await this.deployments.CreateAsync(depModel);
|
||||
|
||||
// Assert
|
||||
Assert.False(string.IsNullOrEmpty(createdDeployment.Id));
|
||||
Assert.Equal(deploymentName, createdDeployment.Name);
|
||||
Assert.Equal(deviceGroupId, createdDeployment.DeviceGroupId);
|
||||
Assert.Equal(priority, createdDeployment.Priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Assert.ThrowsAsync(Type.GetType(expectedException),
|
||||
async () => await this.deployments.CreateAsync(depModel));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public async Task InvalidRmConfigurationTest()
|
||||
{
|
||||
// Arrange
|
||||
var configuration = this.CreateConfiguration(0, false);
|
||||
|
||||
this.registry.Setup(r => r.GetConfigurationAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(configuration);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync(Type.GetType(RESOURCE_NOT_FOUND_EXCEPTION),
|
||||
async () => await this.deployments.GetAsync(configuration.Id));
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(5)]
|
||||
public async Task GetDeploymentsTest(int numDeployments)
|
||||
{
|
||||
// Arrange
|
||||
var configurations = new List<Configuration>();
|
||||
for (int i = numDeployments - 1; i >= 0; i--)
|
||||
{
|
||||
configurations.Add(this.CreateConfiguration(i, true));
|
||||
}
|
||||
|
||||
this.registry.Setup(r => r.GetConfigurationsAsync(20)).ReturnsAsync(configurations);
|
||||
|
||||
// Act
|
||||
var returnedDeployments = await this.deployments.ListAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(numDeployments, returnedDeployments.Items.Count);
|
||||
|
||||
// verify deployments are ordered by name
|
||||
for (int i = 0; i < numDeployments; i++)
|
||||
{
|
||||
Assert.Equal("deployment" + i, returnedDeployments.Items[i].Name);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public async Task GetDeploymentsWithDeviceStatusTest()
|
||||
{
|
||||
// Arrange
|
||||
var configuration = this.CreateConfiguration(0, true);
|
||||
var deploymentId = configuration.Id;
|
||||
this.registry.Setup(r => r.GetConfigurationAsync(deploymentId)).ReturnsAsync(configuration);
|
||||
|
||||
IQuery queryResult = new ResultQuery(3);
|
||||
this.registry.Setup(r => r.CreateQuery(It.IsAny<string>())).Returns(queryResult);
|
||||
|
||||
// Act
|
||||
var returnedDeployment = await this.deployments.GetAsync(deploymentId);
|
||||
var deviceStatuses = returnedDeployment.DeploymentMetrics.DeviceStatuses;
|
||||
|
||||
// Assert
|
||||
Assert.Null(deviceStatuses);
|
||||
|
||||
returnedDeployment = await this.deployments.GetAsync(deploymentId, true);
|
||||
deviceStatuses = returnedDeployment.DeploymentMetrics.DeviceStatuses;
|
||||
Assert.Equal(3, deviceStatuses.Count);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public async Task VerifyGroupAndPackageNameLabelsTest()
|
||||
{
|
||||
// Arrange
|
||||
var deviceGroupId = "dvcGroupId";
|
||||
var deviceGroupName = "dvcGroupName";
|
||||
var deviceGroupQuery = "dvcGroupQuery";
|
||||
var packageName = "packageName";
|
||||
var deploymentName = "depName";
|
||||
var priority = 10;
|
||||
var depModel = new DeploymentServiceModel()
|
||||
{
|
||||
Name = deploymentName,
|
||||
DeviceGroupId = deviceGroupId,
|
||||
DeviceGroupName = deviceGroupName,
|
||||
DeviceGroupQuery = deviceGroupQuery,
|
||||
PackageContent = TEST_PACKAGE_JSON,
|
||||
PackageName = packageName,
|
||||
Priority = priority
|
||||
};
|
||||
|
||||
var newConfig = new Configuration("test-config")
|
||||
{
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ DEPLOYMENT_NAME_LABEL, deploymentName },
|
||||
{ DEPLOYMENT_GROUP_ID_LABEL, deviceGroupId },
|
||||
{ RM_CREATED_LABEL, bool.TrueString },
|
||||
{ DEPLOYMENT_GROUP_NAME_LABEL, deviceGroupName },
|
||||
{ DEPLOYMENT_PACKAGE_NAME_LABEL, packageName }
|
||||
},
|
||||
Priority = priority
|
||||
};
|
||||
|
||||
this.registry.Setup(r => r.AddConfigurationAsync(It.Is<Configuration>(c =>
|
||||
c.Labels.ContainsKey(DEPLOYMENT_NAME_LABEL) &&
|
||||
c.Labels.ContainsKey(DEPLOYMENT_GROUP_ID_LABEL) &&
|
||||
c.Labels.ContainsKey(RM_CREATED_LABEL) &&
|
||||
c.Labels[DEPLOYMENT_NAME_LABEL] == deploymentName &&
|
||||
c.Labels[DEPLOYMENT_GROUP_ID_LABEL] == deviceGroupId &&
|
||||
c.Labels[RM_CREATED_LABEL] == bool.TrueString)))
|
||||
.ReturnsAsync(newConfig);
|
||||
|
||||
// Act
|
||||
var createdDeployment = await this.deployments.CreateAsync(depModel);
|
||||
|
||||
// Assert
|
||||
Assert.False(string.IsNullOrEmpty(createdDeployment.Id));
|
||||
Assert.Equal(deploymentName, createdDeployment.Name);
|
||||
Assert.Equal(deviceGroupId, createdDeployment.DeviceGroupId);
|
||||
Assert.Equal(priority, createdDeployment.Priority);
|
||||
Assert.Equal(deviceGroupName, createdDeployment.DeviceGroupName);
|
||||
Assert.Equal(packageName, createdDeployment.PackageName);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public async Task FilterOutNonRmDeploymentsTest()
|
||||
{
|
||||
// Arrange
|
||||
var configurations = new List<Configuration>
|
||||
{
|
||||
this.CreateConfiguration(0, true),
|
||||
this.CreateConfiguration(1, false)
|
||||
};
|
||||
|
||||
this.registry.Setup(r => r.GetConfigurationsAsync(20))
|
||||
.ReturnsAsync(configurations);
|
||||
|
||||
// Act
|
||||
var returnedDeployments = await this.deployments.ListAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Single(returnedDeployments.Items);
|
||||
Assert.Equal("deployment0", returnedDeployments.Items[0].Name);
|
||||
}
|
||||
|
||||
private Configuration CreateConfiguration(int idx, bool addCreatedByRmLabel)
|
||||
{
|
||||
var conf = new Configuration("test-config"+idx)
|
||||
{
|
||||
Labels = new Dictionary<string, string>()
|
||||
{
|
||||
{ DEPLOYMENT_NAME_LABEL, "deployment" + idx },
|
||||
{ DEPLOYMENT_GROUP_ID_LABEL, "dvcGroupId" + idx }
|
||||
}, Priority = 10
|
||||
};
|
||||
|
||||
if (addCreatedByRmLabel)
|
||||
{
|
||||
conf.Labels.Add(RM_CREATED_LABEL, "true");
|
||||
}
|
||||
|
||||
return conf;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.Devices.Shared;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Exceptions;
|
||||
using Moq;
|
||||
using Services.Test.helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Services.Test
|
||||
{
|
||||
public class DevicesTest
|
||||
{
|
||||
private readonly IDevices devices;
|
||||
private readonly Mock<RegistryManager> registryMock;
|
||||
private readonly string ioTHubHostName = "ioTHubHostName";
|
||||
|
||||
public DevicesTest()
|
||||
{
|
||||
this.registryMock = new Mock<RegistryManager>();
|
||||
this.devices = new Devices(registryMock.Object, this.ioTHubHostName);
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("", "", true)]
|
||||
[InlineData("asdf", "", true)]
|
||||
[InlineData("", "qwer", true)]
|
||||
[InlineData("asdf", "qwer", false)]
|
||||
public async Task GetModuleTwinTest(string deviceId, string moduleId, bool throwsException)
|
||||
{
|
||||
if (throwsException)
|
||||
{
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidInputException>(async () =>
|
||||
await this.devices.GetModuleTwinAsync(deviceId, moduleId));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arrange
|
||||
this.registryMock
|
||||
.Setup(x => x.GetTwinAsync(deviceId, moduleId))
|
||||
.ReturnsAsync(DevicesTest.CreateTestTwin(0));
|
||||
|
||||
// Act
|
||||
var twinSvcModel = await this.devices.GetModuleTwinAsync(deviceId, moduleId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("value0", twinSvcModel.ReportedProperties["test"].ToString());
|
||||
Assert.Equal("value0", twinSvcModel.DesiredProperties["test"].ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("", 5)]
|
||||
[InlineData("2", 5)]
|
||||
[InlineData("6", 5)]
|
||||
public async Task TwinByQueryContinuationTest(string continuationToken, int numResults)
|
||||
{
|
||||
this.registryMock
|
||||
.Setup(x => x.CreateQuery(It.IsAny<string>()))
|
||||
.Returns(new ResultQuery(numResults));
|
||||
|
||||
var queryResult = await this.devices.GetModuleTwinsByQueryAsync("", continuationToken);
|
||||
Assert.Equal("continuationToken", queryResult.ContinuationToken);
|
||||
|
||||
var startIndex = string.IsNullOrEmpty(continuationToken) ? 0 : int.Parse(continuationToken);
|
||||
var total = Math.Max(0, numResults - startIndex);
|
||||
Assert.Equal(total, queryResult.Items.Count);
|
||||
|
||||
for (int i = 0; i < total; i++)
|
||||
{
|
||||
var expectedValue = "value" + (i + startIndex);
|
||||
Assert.Equal(expectedValue, queryResult.Items[i].ReportedProperties["test"].ToString());
|
||||
Assert.Equal(expectedValue, queryResult.Items[i].DesiredProperties["test"].ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("", "SELECT * FROM devices.modules")]
|
||||
[InlineData("deviceId='test'", "SELECT * FROM devices.modules where deviceId='test'")]
|
||||
public async Task GetTwinByQueryTest(string query, string queryToMatch)
|
||||
{
|
||||
this.registryMock
|
||||
.Setup(x => x.CreateQuery(queryToMatch))
|
||||
.Returns(new ResultQuery(3));
|
||||
|
||||
var queryResult = await this.devices.GetModuleTwinsByQueryAsync(query, "");
|
||||
Assert.Equal("continuationToken", queryResult.ContinuationToken);
|
||||
Assert.Equal(3, queryResult.Items.Count);
|
||||
}
|
||||
|
||||
private static Twin CreateTestTwin(int valueToReport)
|
||||
{
|
||||
var twin = new Twin()
|
||||
{
|
||||
Properties = new TwinProperties()
|
||||
};
|
||||
twin.Properties.Reported = new TwinCollection("{\"test\":\"value" + valueToReport + "\"}");
|
||||
twin.Properties.Desired = new TwinCollection("{\"test\":\"value" + valueToReport + "\"}");
|
||||
return twin;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.Devices.Shared;
|
||||
|
||||
namespace Services.Test.helpers
|
||||
{
|
||||
public class ResultQuery : IQuery
|
||||
{
|
||||
private const string DEVICE_ID_KEY = "DeviceId";
|
||||
private readonly List<Twin> results;
|
||||
private readonly List<string> deviceQueryResults;
|
||||
|
||||
public ResultQuery(int numResults)
|
||||
{
|
||||
this.results = new List<Twin>();
|
||||
this.deviceQueryResults = new List<string>();
|
||||
for (int i = 0; i < numResults; i++)
|
||||
{
|
||||
this.results.Add(ResultQuery.CreateTestTwin(i));
|
||||
this.deviceQueryResults.Add("{" + $"'{DEVICE_ID_KEY}':'device{i}'" + "}");
|
||||
this.HasMoreResults = true;
|
||||
}
|
||||
}
|
||||
public Task<IEnumerable<Twin>> GetNextAsTwinAsync()
|
||||
{
|
||||
this.HasMoreResults = false;
|
||||
return Task.FromResult<IEnumerable<Twin>>(this.results);
|
||||
}
|
||||
|
||||
public Task<QueryResponse<Twin>> GetNextAsTwinAsync(QueryOptions options)
|
||||
{
|
||||
this.HasMoreResults = false;
|
||||
QueryResponse<Twin> resultResponse;
|
||||
|
||||
if (string.IsNullOrEmpty(options.ContinuationToken))
|
||||
{
|
||||
resultResponse = new QueryResponse<Twin>(this.results, "continuationToken");
|
||||
}
|
||||
else
|
||||
{
|
||||
var index = int.Parse(options.ContinuationToken);
|
||||
var count = this.results.Count - index;
|
||||
|
||||
var continuedResults = new List<Twin>();
|
||||
if (index < count)
|
||||
{
|
||||
continuedResults = this.results.GetRange(index, count);
|
||||
}
|
||||
resultResponse = new QueryResponse<Twin>(continuedResults, "continuationToken");
|
||||
}
|
||||
|
||||
return Task.FromResult(resultResponse);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<DeviceJob>> GetNextAsDeviceJobAsync()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<QueryResponse<DeviceJob>> GetNextAsDeviceJobAsync(QueryOptions options)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<JobResponse>> GetNextAsJobResponseAsync()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<QueryResponse<JobResponse>> GetNextAsJobResponseAsync(QueryOptions options)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> GetNextAsJsonAsync()
|
||||
{
|
||||
this.HasMoreResults = false;
|
||||
return Task.FromResult(deviceQueryResults.AsEnumerable());
|
||||
}
|
||||
|
||||
public Task<QueryResponse<string>> GetNextAsJsonAsync(QueryOptions options)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool HasMoreResults { get; set; }
|
||||
|
||||
private static Twin CreateTestTwin(int valueToReport)
|
||||
{
|
||||
var twin = new Twin()
|
||||
{
|
||||
Properties = new TwinProperties()
|
||||
};
|
||||
twin.Properties.Reported = new TwinCollection("{\"test\":\"value" + valueToReport + "\"}");
|
||||
twin.Properties.Desired = new TwinCollection("{\"test\":\"value" + valueToReport + "\"}");
|
||||
return twin;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Helpers;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Runtime;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
||||
{
|
||||
public interface IDeployments
|
||||
{
|
||||
Task<DeploymentServiceModel> CreateAsync(DeploymentServiceModel model);
|
||||
Task<DeploymentServiceListModel> ListAsync();
|
||||
Task<DeploymentServiceModel> GetAsync(string id, bool includeDeviceStatus);
|
||||
Task DeleteAsync(string deploymentId);
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum DeploymentStatus
|
||||
{
|
||||
Pending, Successful, Failed
|
||||
}
|
||||
|
||||
public class Deployments : IDeployments
|
||||
{
|
||||
private const int MAX_DEPLOYMENTS = 20;
|
||||
|
||||
private const string DEPLOYMENT_NAME_LABEL = "Name";
|
||||
private const string DEPLOYMENT_GROUP_ID_LABEL = "DeviceGroupId";
|
||||
private const string DEPLOYMENT_GROUP_NAME_LABEL = "DeviceGroupName";
|
||||
private const string DEPLOYMENT_PACKAGE_NAME_LABEL = "PackageName";
|
||||
private const string RM_CREATED_LABEL = "RMDeployment";
|
||||
|
||||
private const string DEVICE_GROUP_ID_PARAM = "deviceGroupId";
|
||||
private const string DEVICE_GROUP_QUERY_PARAM = "deviceGroupQuery";
|
||||
private const string NAME_PARAM = "name";
|
||||
private const string PACKAGE_CONTENT_PARAM = "packageContent";
|
||||
private const string PRIORITY_PARAM = "priority";
|
||||
|
||||
private const string DEVICE_ID_KEY = "DeviceId";
|
||||
private const string EDGE_MANIFEST_SCHEMA = "schemaVersion";
|
||||
|
||||
private const string APPLIED_DEVICES_QUERY =
|
||||
"select deviceId from devices.modules where moduleId = '$edgeAgent'" +
|
||||
" and configurations.[[{0}]].status = 'Applied'";
|
||||
|
||||
private const string SUCCESSFUL_DEVICES_QUERY = APPLIED_DEVICES_QUERY +
|
||||
" and properties.desired.$version = properties.reported.lastDesiredVersion" +
|
||||
" and properties.reported.lastDesiredStatus.code = 200";
|
||||
|
||||
private const string FAILED_DEVICES_QUERY = APPLIED_DEVICES_QUERY +
|
||||
" and properties.desired.$version = properties.reported.lastDesiredVersion" +
|
||||
" and properties.reported.lastDesiredStatus.code != 200";
|
||||
|
||||
private RegistryManager registry;
|
||||
private string ioTHubHostName;
|
||||
private readonly ILogger log;
|
||||
|
||||
public Deployments(
|
||||
IServicesConfig config,
|
||||
ILogger logger)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
IoTHubConnectionHelper.CreateUsingHubConnectionString(config.IoTHubConnString, (conn) =>
|
||||
{
|
||||
this.registry = RegistryManager.CreateFromConnectionString(conn);
|
||||
this.ioTHubHostName = IotHubConnectionStringBuilder.Create(conn).HostName;
|
||||
});
|
||||
|
||||
this.log = logger;
|
||||
}
|
||||
|
||||
public Deployments(
|
||||
RegistryManager registry,
|
||||
string ioTHubHostName)
|
||||
{
|
||||
this.registry = registry;
|
||||
this.ioTHubHostName = ioTHubHostName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules a deployment of the provided package, to the given group.
|
||||
/// </summary>
|
||||
/// <returns>Scheduled deployment</returns>
|
||||
public async Task<DeploymentServiceModel> CreateAsync(DeploymentServiceModel model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(model.DeviceGroupId))
|
||||
{
|
||||
throw new ArgumentNullException(DEVICE_GROUP_ID_PARAM);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.DeviceGroupQuery))
|
||||
{
|
||||
throw new ArgumentNullException(DEVICE_GROUP_QUERY_PARAM);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.Name))
|
||||
{
|
||||
throw new ArgumentNullException(NAME_PARAM);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.PackageContent))
|
||||
{
|
||||
throw new ArgumentNullException(PACKAGE_CONTENT_PARAM);
|
||||
}
|
||||
|
||||
if (model.Priority < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(PRIORITY_PARAM,
|
||||
model.Priority,
|
||||
"The priority provided should be 0 or greater");
|
||||
}
|
||||
|
||||
var edgeConfiguration = this.CreateEdgeConfiguration(model);
|
||||
|
||||
// TODO: Add specific exception handling when exception types are exposed
|
||||
// https://github.com/Azure/azure-iot-sdk-csharp/issues/649
|
||||
return new DeploymentServiceModel(await this.registry.AddConfigurationAsync(edgeConfiguration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all deployments that have been scheduled on the iothub.
|
||||
/// Only deployments which were created by RM will be returned.
|
||||
/// </summary>
|
||||
/// <returns>All scheduled deployments with RMDeployment label</returns>
|
||||
public async Task<DeploymentServiceListModel> ListAsync()
|
||||
{
|
||||
// TODO: Currently they only support 20 deployments
|
||||
var deployments = await this.registry.GetConfigurationsAsync(MAX_DEPLOYMENTS);
|
||||
|
||||
if (deployments == null)
|
||||
{
|
||||
throw new ResourceNotFoundException($"No deployments found for {this.ioTHubHostName} hub.");
|
||||
}
|
||||
|
||||
List<DeploymentServiceModel> serviceModelDeployments =
|
||||
deployments.Where(this.CheckIfDeploymentWasMadeByRM)
|
||||
.Select(config => new DeploymentServiceModel(config))
|
||||
.OrderBy(conf => conf.Name)
|
||||
.ToList();
|
||||
|
||||
return new DeploymentServiceListModel(serviceModelDeployments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve information on a single deployment given its id.
|
||||
/// If includeDeviceStatus is included additional queries are created to retrieve the status of
|
||||
/// the deployment per device.
|
||||
/// </summary>
|
||||
/// <returns>Deployment for the given id</returns>
|
||||
public async Task<DeploymentServiceModel> GetAsync(string deploymentId, bool includeDeviceStatus = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(deploymentId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deploymentId));
|
||||
}
|
||||
|
||||
var deployment = await this.registry.GetConfigurationAsync(deploymentId);
|
||||
if (deployment == null)
|
||||
{
|
||||
throw new ResourceNotFoundException($"Deployment with id {deploymentId} not found.");
|
||||
}
|
||||
|
||||
if (!this.CheckIfDeploymentWasMadeByRM(deployment))
|
||||
{
|
||||
throw new ResourceNotSupportedException($"Deployment with id {deploymentId}" + @" was
|
||||
created externally and therefore not supported");
|
||||
}
|
||||
|
||||
return new DeploymentServiceModel(deployment)
|
||||
{
|
||||
DeploymentMetrics =
|
||||
{
|
||||
DeviceStatuses = includeDeviceStatus ? this.GetDeviceStatuses(deploymentId) : null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a given deployment by id.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task DeleteAsync(string deploymentId)
|
||||
{
|
||||
if(string.IsNullOrEmpty(deploymentId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deploymentId));
|
||||
}
|
||||
|
||||
await this.registry.RemoveConfigurationAsync(deploymentId);
|
||||
}
|
||||
|
||||
private Configuration CreateEdgeConfiguration(DeploymentServiceModel model)
|
||||
{
|
||||
var deploymentId = Guid.NewGuid().ToString().ToLower();
|
||||
var edgeConfiguration = new Configuration(deploymentId);
|
||||
var packageEdgeConfiguration = JsonConvert.DeserializeObject<Configuration>(model.PackageContent);
|
||||
edgeConfiguration.Content = packageEdgeConfiguration.Content;
|
||||
|
||||
var targetCondition = QueryConditionTranslator.ToQueryString(model.DeviceGroupQuery);
|
||||
edgeConfiguration.TargetCondition = string.IsNullOrEmpty(targetCondition) ? "*" : targetCondition;
|
||||
edgeConfiguration.Priority = model.Priority;
|
||||
edgeConfiguration.ETag = string.Empty;
|
||||
|
||||
if(edgeConfiguration.Labels == null)
|
||||
{
|
||||
edgeConfiguration.Labels = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
// Required labels
|
||||
edgeConfiguration.Labels.Add(DEPLOYMENT_NAME_LABEL, model.Name);
|
||||
edgeConfiguration.Labels.Add(DEPLOYMENT_GROUP_ID_LABEL, model.DeviceGroupId);
|
||||
edgeConfiguration.Labels.Add(RM_CREATED_LABEL, bool.TrueString);
|
||||
|
||||
// Add optional labels
|
||||
if (model.DeviceGroupName != null)
|
||||
{
|
||||
edgeConfiguration.Labels.Add(DEPLOYMENT_GROUP_NAME_LABEL, model.DeviceGroupName);
|
||||
}
|
||||
if (model.PackageName != null)
|
||||
{
|
||||
edgeConfiguration.Labels.Add(DEPLOYMENT_PACKAGE_NAME_LABEL, model.PackageName);
|
||||
}
|
||||
|
||||
return edgeConfiguration;
|
||||
}
|
||||
|
||||
private bool CheckIfDeploymentWasMadeByRM(Configuration conf)
|
||||
{
|
||||
return conf.Labels != null &&
|
||||
conf.Labels.ContainsKey(RM_CREATED_LABEL) &&
|
||||
bool.TryParse(conf.Labels[RM_CREATED_LABEL], out var res) && res;
|
||||
}
|
||||
|
||||
private IDictionary<string, DeploymentStatus> GetDeviceStatuses(string deploymentId)
|
||||
{
|
||||
var appliedDevices = this.GetDevicesInQuery(APPLIED_DEVICES_QUERY, deploymentId);
|
||||
var successfulDevices = this.GetDevicesInQuery(SUCCESSFUL_DEVICES_QUERY, deploymentId);
|
||||
var failedDevices = this.GetDevicesInQuery(FAILED_DEVICES_QUERY, deploymentId);
|
||||
|
||||
var deviceWithStatus = new Dictionary<string, DeploymentStatus>();
|
||||
|
||||
foreach (var successfulDevice in successfulDevices)
|
||||
{
|
||||
deviceWithStatus.Add(successfulDevice, DeploymentStatus.Successful);
|
||||
}
|
||||
|
||||
foreach (var failedDevice in failedDevices)
|
||||
{
|
||||
deviceWithStatus.Add(failedDevice, DeploymentStatus.Failed);
|
||||
}
|
||||
|
||||
foreach (var device in appliedDevices)
|
||||
{
|
||||
if (!successfulDevices.Contains(device) && !failedDevices.Contains(device))
|
||||
{
|
||||
deviceWithStatus.Add(device, DeploymentStatus.Pending);
|
||||
}
|
||||
}
|
||||
|
||||
return deviceWithStatus;
|
||||
}
|
||||
|
||||
private HashSet<string> GetDevicesInQuery(string hubQuery, string deploymentId)
|
||||
{
|
||||
var query = string.Format(hubQuery, deploymentId);
|
||||
var queryResponse = this.registry.CreateQuery(query);
|
||||
var deviceIds = new HashSet<string>();
|
||||
|
||||
try
|
||||
{
|
||||
while (queryResponse.HasMoreResults)
|
||||
{
|
||||
// TODO: Add pagination with queryOptions
|
||||
var resultSet = queryResponse.GetNextAsJsonAsync();
|
||||
foreach (var result in resultSet.Result)
|
||||
{
|
||||
var deviceId = JToken.Parse(result)[DEVICE_ID_KEY];
|
||||
deviceIds.Add(deviceId.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.log.Error($"Error getting status of devices in query {query}", () => new { ex.Message });
|
||||
}
|
||||
|
||||
return deviceIds;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
||||
{
|
||||
|
||||
public delegate Task<DevicePropertyServiceModel> DevicePropertyDelegate(DevicePropertyServiceModel model);
|
||||
public interface IDevices
|
||||
{
|
||||
|
@ -26,12 +25,15 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
|||
Task<DeviceServiceModel> CreateAsync(DeviceServiceModel toServiceModel);
|
||||
Task<DeviceServiceModel> CreateOrUpdateAsync(DeviceServiceModel toServiceModel, DevicePropertyDelegate devicePropertyDelegate);
|
||||
Task DeleteAsync(string id);
|
||||
Task<TwinServiceModel> GetModuleTwinAsync(string deviceId, string moduleId);
|
||||
Task<TwinServiceListModel> GetModuleTwinsByQueryAsync(string query, string continuationToken);
|
||||
}
|
||||
|
||||
public class Devices : IDevices
|
||||
{
|
||||
private const int MAX_GET_LIST = 1000;
|
||||
private const string QUERY_PREFIX = "SELECT * FROM devices";
|
||||
private const string MODULE_QUERY_PREFIX = "SELECT * FROM devices.modules";
|
||||
|
||||
private RegistryManager registry;
|
||||
private string ioTHubHostName;
|
||||
|
@ -51,6 +53,12 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
|||
});
|
||||
}
|
||||
|
||||
public Devices(RegistryManager registry, string ioTHubHostName)
|
||||
{
|
||||
this.registry = registry;
|
||||
this.ioTHubHostName = ioTHubHostName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query devices
|
||||
/// </summary>
|
||||
|
@ -72,7 +80,10 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
|||
// normally we need deviceTwins for all devices to show device list
|
||||
var devices = await this.registry.GetDevicesAsync(MAX_GET_LIST);
|
||||
|
||||
var twins = await this.GetTwinByQueryAsync(query, continuationToken, MAX_GET_LIST);
|
||||
var twins = await this.GetTwinByQueryAsync(QUERY_PREFIX,
|
||||
query,
|
||||
continuationToken,
|
||||
MAX_GET_LIST);
|
||||
|
||||
// since deviceAsync does not support continuationToken for now, we need to ignore those devices which does not shown in twins
|
||||
return new DeviceServiceListModel(devices
|
||||
|
@ -179,16 +190,46 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
|||
await this.registry.RemoveDeviceAsync(id);
|
||||
}
|
||||
|
||||
public async Task<TwinServiceModel> GetModuleTwinAsync(string deviceId, string moduleId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
throw new InvalidInputException("A valid deviceId must be provided.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(moduleId))
|
||||
{
|
||||
throw new InvalidInputException("A valid moduleId must be provided.");
|
||||
}
|
||||
|
||||
var twin = await this.registry.GetTwinAsync(deviceId, moduleId);
|
||||
return new TwinServiceModel(twin);
|
||||
}
|
||||
|
||||
public async Task<TwinServiceListModel> GetModuleTwinsByQueryAsync(string query,
|
||||
string continuationToken)
|
||||
{
|
||||
var twins = await this.GetTwinByQueryAsync(MODULE_QUERY_PREFIX,
|
||||
query,
|
||||
continuationToken,
|
||||
MAX_GET_LIST);
|
||||
var result = twins.Result.Select(twin => new TwinServiceModel(twin)).ToList();
|
||||
|
||||
return new TwinServiceListModel(result, twins.ContinuationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get twin result by query
|
||||
/// </summary>
|
||||
/// <param name="queryPrefix">The query prefix which selects devices or device modules</param>
|
||||
/// <param name="query">The query without prefix</param>
|
||||
/// <param name="continuationToken">The continuationToken</param>
|
||||
/// <param name="nubmerOfResult">The max result</param>
|
||||
/// <param name="numberOfResult">The max result</param>
|
||||
/// <returns></returns>
|
||||
private async Task<ResultWithContinuationToken<List<Twin>>> GetTwinByQueryAsync(string query, string continuationToken, int nubmerOfResult)
|
||||
private async Task<ResultWithContinuationToken<List<Twin>>> GetTwinByQueryAsync(string queryPrefix,
|
||||
string query, string continuationToken, int numberOfResult)
|
||||
{
|
||||
query = string.IsNullOrEmpty(query) ? QUERY_PREFIX : $"{QUERY_PREFIX} where {query}";
|
||||
query = string.IsNullOrEmpty(query) ? queryPrefix : $"{queryPrefix} where {query}";
|
||||
|
||||
var twins = new List<Twin>();
|
||||
|
||||
|
@ -197,7 +238,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
|||
QueryOptions options = new QueryOptions();
|
||||
options.ContinuationToken = continuationToken;
|
||||
|
||||
while (twinQuery.HasMoreResults && twins.Count < nubmerOfResult)
|
||||
while (twinQuery.HasMoreResults && twins.Count < numberOfResult)
|
||||
{
|
||||
var response = await twinQuery.GetNextAsTwinAsync(options);
|
||||
options.ContinuationToken = response.ContinuationToken;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// This exception is thrown when a client is requesting a resource that
|
||||
/// was created outside of remote monitoring specifically by id.
|
||||
/// </summary>
|
||||
public class ResourceNotSupportedException : Exception
|
||||
{
|
||||
public ResourceNotSupportedException()
|
||||
{
|
||||
}
|
||||
|
||||
public ResourceNotSupportedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ResourceNotSupportedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
|||
Task<JobServiceModel> ScheduleTwinUpdateAsync(
|
||||
string jobId,
|
||||
string queryCondition,
|
||||
DeviceTwinServiceModel twin,
|
||||
TwinServiceModel twin,
|
||||
DateTimeOffset startTimeUtc,
|
||||
long maxExecutionTimeInSeconds);
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services
|
|||
public async Task<JobServiceModel> ScheduleTwinUpdateAsync(
|
||||
string jobId,
|
||||
string queryCondition,
|
||||
DeviceTwinServiceModel twin,
|
||||
TwinServiceModel twin,
|
||||
DateTimeOffset startTimeUtc,
|
||||
long maxExecutionTimeInSeconds)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.Devices;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Statistics exposed by configuration queries
|
||||
/// </summary>
|
||||
public class DeploymentMetrics
|
||||
{
|
||||
public IDictionary<string, long> Metrics { get; set; }
|
||||
public IDictionary<string, DeploymentStatus> DeviceStatuses { get; set; }
|
||||
|
||||
public DeploymentMetrics(ConfigurationMetrics systemMetrics, ConfigurationMetrics customMetrics)
|
||||
{
|
||||
this.Metrics = new Dictionary<string, long>();
|
||||
this.DeviceStatuses = new Dictionary<string, DeploymentStatus>();
|
||||
|
||||
if (systemMetrics?.Results?.Count > 0)
|
||||
{
|
||||
foreach (var pair in systemMetrics.Results)
|
||||
{
|
||||
this.Metrics.Add(pair);
|
||||
}
|
||||
}
|
||||
|
||||
if (customMetrics?.Results?.Count > 0)
|
||||
{
|
||||
foreach (var pair in customMetrics.Results)
|
||||
{
|
||||
this.Metrics.Add(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
||||
{
|
||||
public class DeploymentServiceListModel
|
||||
{
|
||||
public List<DeploymentServiceModel> Items { get; set; }
|
||||
|
||||
public DeploymentServiceListModel(List<DeploymentServiceModel> items)
|
||||
{
|
||||
this.Items = items;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Azure.Devices;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
||||
{
|
||||
public class DeploymentServiceModel
|
||||
{
|
||||
private const string DEPLOYMENT_NAME_LABEL = "Name";
|
||||
private const string DEPLOYMENT_GROUP_ID_LABEL = "DeviceGroupId";
|
||||
private const string DEPLOYMENT_GROUP_NAME_LABEL = "DeviceGroupName";
|
||||
private const string DEPLOYMENT_PACKAGE_NAME_LABEL = "PackageName";
|
||||
private const string RM_CREATED_LABEL = "RMDeployment";
|
||||
|
||||
public DateTime CreatedDateTimeUtc { get; set; }
|
||||
public string Id { get; set; }
|
||||
public DeploymentMetrics DeploymentMetrics { get; set; }
|
||||
public string DeviceGroupId { get; set; }
|
||||
public string DeviceGroupName { get; set; }
|
||||
public string DeviceGroupQuery { get; set; }
|
||||
public string Name {get; set; }
|
||||
public string PackageContent { get; set; }
|
||||
public string PackageName { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public DeploymentType Type { get; set; }
|
||||
|
||||
public DeploymentServiceModel()
|
||||
{
|
||||
}
|
||||
|
||||
public DeploymentServiceModel(Configuration config)
|
||||
{
|
||||
if (string.IsNullOrEmpty(config.Id))
|
||||
{
|
||||
throw new ArgumentException($"Invalid deploymentId provided {config.Id}");
|
||||
}
|
||||
|
||||
this.VerifyConfigurationLabel(config, DEPLOYMENT_NAME_LABEL);
|
||||
this.VerifyConfigurationLabel(config, DEPLOYMENT_GROUP_ID_LABEL);
|
||||
this.VerifyConfigurationLabel(config, RM_CREATED_LABEL);
|
||||
|
||||
this.Id = config.Id;
|
||||
this.Name = config.Labels[DEPLOYMENT_NAME_LABEL];
|
||||
this.CreatedDateTimeUtc = config.CreatedTimeUtc;
|
||||
this.DeviceGroupId = config.Labels[DEPLOYMENT_GROUP_ID_LABEL];
|
||||
|
||||
if (config.Labels.ContainsKey(DEPLOYMENT_GROUP_NAME_LABEL))
|
||||
{
|
||||
this.DeviceGroupName = config.Labels[DEPLOYMENT_GROUP_NAME_LABEL];
|
||||
}
|
||||
|
||||
if (config.Labels.ContainsKey(DEPLOYMENT_PACKAGE_NAME_LABEL))
|
||||
{
|
||||
this.PackageName = config.Labels[DEPLOYMENT_PACKAGE_NAME_LABEL];
|
||||
}
|
||||
|
||||
this.Priority = config.Priority;
|
||||
this.Type = DeploymentType.EdgeManifest;
|
||||
|
||||
this.DeploymentMetrics = new DeploymentMetrics(config.SystemMetrics, config.Metrics);
|
||||
}
|
||||
|
||||
private void VerifyConfigurationLabel(Configuration config, string labelName)
|
||||
{
|
||||
if (!config.Labels.ContainsKey(labelName))
|
||||
{
|
||||
throw new ArgumentException($"Configuration is missing necessary label {labelName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DeploymentType {
|
||||
EdgeManifest
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
|||
public bool Connected { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public DateTime LastStatusUpdated { get; set; }
|
||||
public DeviceTwinServiceModel Twin { get; set; }
|
||||
public TwinServiceModel Twin { get; set; }
|
||||
public string IoTHubHostName { get; set; }
|
||||
public AuthenticationMechanismServiceModel Authentication { get; set; }
|
||||
|
||||
|
@ -27,7 +27,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
|||
bool connected,
|
||||
bool enabled,
|
||||
DateTime lastStatusUpdated,
|
||||
DeviceTwinServiceModel twin,
|
||||
TwinServiceModel twin,
|
||||
AuthenticationMechanismServiceModel authentication,
|
||||
string ioTHubHostName)
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
|||
this.Authentication = authentication;
|
||||
}
|
||||
|
||||
public DeviceServiceModel(Device azureDevice, DeviceTwinServiceModel twin, string ioTHubHostName) :
|
||||
public DeviceServiceModel(Device azureDevice, TwinServiceModel twin, string ioTHubHostName) :
|
||||
this(
|
||||
etag: azureDevice.ETag,
|
||||
id: azureDevice.Id,
|
||||
|
@ -59,7 +59,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
|||
}
|
||||
|
||||
public DeviceServiceModel(Device azureDevice, Twin azureTwin, string ioTHubHostName) :
|
||||
this(azureDevice, new DeviceTwinServiceModel(azureTwin), ioTHubHostName)
|
||||
this(azureDevice, new TwinServiceModel(azureTwin), ioTHubHostName)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
|||
|
||||
public MethodParameterServiceModel MethodParameter { get; set; }
|
||||
|
||||
public DeviceTwinServiceModel UpdateTwin { get; set; }
|
||||
public TwinServiceModel UpdateTwin { get; set; }
|
||||
|
||||
public string FailureReason { get; set; }
|
||||
|
||||
|
@ -89,7 +89,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
|||
|
||||
if (jobResponse.UpdateTwin != null)
|
||||
{
|
||||
this.UpdateTwin = new DeviceTwinServiceModel(jobResponse.UpdateTwin);
|
||||
this.UpdateTwin = new TwinServiceModel(jobResponse.UpdateTwin);
|
||||
}
|
||||
|
||||
this.FailureReason = jobResponse.FailureReason;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
||||
{
|
||||
public class TwinServiceListModel
|
||||
{
|
||||
public string ContinuationToken { get; set; }
|
||||
|
||||
public List<TwinServiceModel> Items { get; set; }
|
||||
|
||||
public TwinServiceListModel(IEnumerable<TwinServiceModel> twins, string continuationToken = null)
|
||||
{
|
||||
this.ContinuationToken = continuationToken;
|
||||
this.Items = new List<TwinServiceModel>(twins);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,44 +7,68 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
||||
{
|
||||
public class DeviceTwinServiceModel
|
||||
public class TwinServiceModel
|
||||
{
|
||||
public string ETag { get; set; }
|
||||
public string DeviceId { get; set; }
|
||||
public string ModuleId { get; set; }
|
||||
public bool IsEdgeDevice { get; set; }
|
||||
public bool IsSimulated { get; set; }
|
||||
public Dictionary<string, JToken> DesiredProperties { get; set; }
|
||||
public Dictionary<string, JToken> ReportedProperties { get; set; }
|
||||
public Dictionary<string, JToken> Tags { get; set; }
|
||||
|
||||
public DeviceTwinServiceModel()
|
||||
public TwinServiceModel()
|
||||
{
|
||||
}
|
||||
|
||||
public DeviceTwinServiceModel(
|
||||
public TwinServiceModel(
|
||||
string etag,
|
||||
string deviceId,
|
||||
Dictionary<string, JToken> desiredProperties,
|
||||
Dictionary<string, JToken> reportedProperties,
|
||||
Dictionary<string, JToken> tags,
|
||||
bool isSimulated)
|
||||
bool isSimulated) : this(
|
||||
etag: etag,
|
||||
deviceId: deviceId,
|
||||
desiredProperties: desiredProperties,
|
||||
reportedProperties: reportedProperties,
|
||||
tags: tags,
|
||||
isSimulated: isSimulated,
|
||||
isEdgeDevice: false
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public TwinServiceModel(
|
||||
string etag,
|
||||
string deviceId,
|
||||
Dictionary<string, JToken> desiredProperties,
|
||||
Dictionary<string, JToken> reportedProperties,
|
||||
Dictionary<string, JToken> tags,
|
||||
bool isSimulated,
|
||||
bool isEdgeDevice)
|
||||
{
|
||||
this.ETag = etag;
|
||||
this.DeviceId = deviceId;
|
||||
this.DesiredProperties = desiredProperties;
|
||||
this.ReportedProperties = reportedProperties;
|
||||
this.Tags = tags;
|
||||
this.IsEdgeDevice = isEdgeDevice;
|
||||
this.IsSimulated = isSimulated;
|
||||
}
|
||||
|
||||
public DeviceTwinServiceModel(Twin twin)
|
||||
public TwinServiceModel(Twin twin)
|
||||
{
|
||||
if (twin != null)
|
||||
{
|
||||
this.ETag = twin.ETag;
|
||||
this.DeviceId = twin.DeviceId;
|
||||
this.ModuleId = twin.ModuleId;
|
||||
this.Tags = TwinCollectionToDictionary(twin.Tags);
|
||||
this.DesiredProperties = TwinCollectionToDictionary(twin.Properties.Desired);
|
||||
this.ReportedProperties = TwinCollectionToDictionary(twin.Properties.Reported);
|
||||
this.IsEdgeDevice = twin.Capabilities?.IotEdge ?? false;
|
||||
this.IsSimulated = this.Tags.ContainsKey("IsSimulated") && this.Tags["IsSimulated"].ToString() == "Y";
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +85,11 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models
|
|||
{
|
||||
ETag = this.ETag,
|
||||
Tags = DictionaryToTwinCollection(this.Tags),
|
||||
Properties = properties
|
||||
Properties = properties,
|
||||
Capabilities = this.IsEdgeDevice ? new DeviceCapabilities()
|
||||
{
|
||||
IotEdge = this.IsEdgeDevice
|
||||
} : null
|
||||
};
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.Services.Runtime
|
|||
public string IoTHubConnString { get; set; }
|
||||
public string StorageAdapterApiUrl { get; set; }
|
||||
public string UserManagementApiUrl { get; set; }
|
||||
public string ConfigServiceUri { get; set; }
|
||||
public string DevicePropertiesWhiteList { get; set; }
|
||||
public long DevicePropertiesTTL { get; set; }
|
||||
public long DevicePropertiesRebuildTimeout { get; set; }
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<RootNamespace>Microsoft.Azure.IoTSolutions.IotHubManager.Services</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Devices" Version="1.4.1" />
|
||||
<PackageReference Include="Microsoft.Azure.Devices" Version="1.17.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Controllers;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models;
|
||||
using Moq;
|
||||
using WebService.Test.helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace WebService.Test.v1.Controllers
|
||||
{
|
||||
public class DeploymentsControllerTest
|
||||
{
|
||||
private readonly DeploymentsController deploymentsController;
|
||||
private readonly Mock<IDeployments> deploymentsMock;
|
||||
private const string DEPLOYMENT_NAME = "depname";
|
||||
private const string DEVICE_GROUP_ID = "dvcGroupId";
|
||||
private const string DEVICE_GROUP_NAME = "dvcGroupName";
|
||||
private const string DEVICE_GROUP_QUERY = "dvcGroupQuery";
|
||||
private const string PACKAGE_CONTENT = "{}";
|
||||
private const string PACKAGE_NAME = "packageName";
|
||||
private const string DEPLOYMENT_ID = "dvcGroupId-packageId";
|
||||
private const int PRIORITY = 10;
|
||||
|
||||
public DeploymentsControllerTest()
|
||||
{
|
||||
this.deploymentsMock = new Mock<IDeployments>();
|
||||
this.deploymentsController = new DeploymentsController(this.deploymentsMock.Object);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public async Task GetDeploymentTest()
|
||||
{
|
||||
// Arrange
|
||||
this.deploymentsMock.Setup(x => x.GetAsync(DEPLOYMENT_ID, false)).ReturnsAsync(new DeploymentServiceModel()
|
||||
{
|
||||
Name = DEPLOYMENT_NAME,
|
||||
DeviceGroupId = DEVICE_GROUP_ID,
|
||||
DeviceGroupName = DEVICE_GROUP_NAME,
|
||||
DeviceGroupQuery = DEVICE_GROUP_QUERY,
|
||||
PackageContent = PACKAGE_CONTENT,
|
||||
PackageName = PACKAGE_NAME,
|
||||
Priority = PRIORITY,
|
||||
Id = DEPLOYMENT_ID,
|
||||
Type = DeploymentType.EdgeManifest,
|
||||
CreatedDateTimeUtc = DateTime.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await this.deploymentsController.GetAsync(DEPLOYMENT_ID);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DEPLOYMENT_ID, result.DeploymentId);
|
||||
Assert.Equal(DEPLOYMENT_NAME, result.Name);
|
||||
Assert.Equal(PACKAGE_CONTENT, result.PackageContent);
|
||||
Assert.Equal(PACKAGE_NAME, result.PackageName);
|
||||
Assert.Equal(DEVICE_GROUP_ID, result.DeviceGroupId);
|
||||
Assert.Equal(DEVICE_GROUP_NAME, result.DeviceGroupName);
|
||||
Assert.Equal(PRIORITY, result.Priority);
|
||||
Assert.Equal(DeploymentType.EdgeManifest, result.Type);
|
||||
Assert.True((DateTimeOffset.UtcNow - result.CreatedDateTimeUtc).TotalSeconds < 5);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public async Task VerifyGroupAndPackageNameLabelsTest()
|
||||
{
|
||||
// Arrange
|
||||
this.deploymentsMock.Setup(x => x.GetAsync(DEPLOYMENT_ID, false)).ReturnsAsync(new DeploymentServiceModel()
|
||||
{
|
||||
Name = DEPLOYMENT_NAME,
|
||||
DeviceGroupId = DEVICE_GROUP_ID,
|
||||
DeviceGroupName = DEVICE_GROUP_NAME,
|
||||
DeviceGroupQuery = DEVICE_GROUP_QUERY,
|
||||
PackageContent = PACKAGE_CONTENT,
|
||||
Priority = PRIORITY,
|
||||
Id = DEPLOYMENT_ID,
|
||||
Type = DeploymentType.EdgeManifest,
|
||||
CreatedDateTimeUtc = DateTime.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await this.deploymentsController.GetAsync(DEPLOYMENT_ID);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DEPLOYMENT_ID, result.DeploymentId);
|
||||
Assert.Equal(DEPLOYMENT_NAME, result.Name);
|
||||
Assert.Equal(PACKAGE_CONTENT, result.PackageContent);
|
||||
Assert.Equal(DEVICE_GROUP_ID, result.DeviceGroupId);
|
||||
Assert.Equal(PRIORITY, result.Priority);
|
||||
Assert.Equal(DeploymentType.EdgeManifest, result.Type);
|
||||
Assert.True((DateTimeOffset.UtcNow - result.CreatedDateTimeUtc).TotalSeconds < 5);
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(5)]
|
||||
public async Task GetDeploymentsTest(int numDeployments)
|
||||
{
|
||||
// Arrange
|
||||
var deploymentsList = new List<DeploymentServiceModel>();
|
||||
for (var i = 0; i < numDeployments; i++)
|
||||
{
|
||||
deploymentsList.Add(new DeploymentServiceModel()
|
||||
{
|
||||
Name = DEPLOYMENT_NAME + i,
|
||||
DeviceGroupId = DEVICE_GROUP_ID + i,
|
||||
DeviceGroupQuery = DEVICE_GROUP_QUERY + i,
|
||||
PackageContent = PACKAGE_CONTENT + i,
|
||||
Priority = PRIORITY + i,
|
||||
Id = DEPLOYMENT_ID + i,
|
||||
Type = DeploymentType.EdgeManifest,
|
||||
CreatedDateTimeUtc = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
this.deploymentsMock.Setup(x => x.ListAsync()).ReturnsAsync(
|
||||
new DeploymentServiceListModel(deploymentsList)
|
||||
);
|
||||
|
||||
// Act
|
||||
var results = await this.deploymentsController.GetAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(numDeployments, results.Items.Count);
|
||||
for (var i = 0; i < numDeployments; i++)
|
||||
{
|
||||
var result = results.Items[i];
|
||||
Assert.Equal(DEPLOYMENT_ID + i, result.DeploymentId);
|
||||
Assert.Equal(DEPLOYMENT_NAME + i, result.Name);
|
||||
Assert.Equal(DEVICE_GROUP_QUERY + i, result.DeviceGroupQuery);
|
||||
Assert.Equal(DEVICE_GROUP_ID + i, result.DeviceGroupId);
|
||||
Assert.Equal(PACKAGE_CONTENT + i, result.PackageContent);
|
||||
Assert.Equal(PRIORITY + i, result.Priority);
|
||||
Assert.Equal(DeploymentType.EdgeManifest, result.Type);
|
||||
Assert.True((DateTimeOffset.UtcNow - result.CreatedDateTimeUtc).TotalSeconds < 5);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("depName", "dvcGroupId", "dvcQuery", "pkgContent", 10, false)]
|
||||
[InlineData("", "dvcGroupId", "dvcQuery", "pkgContent", 10, true)]
|
||||
[InlineData("depName", "", "dvcQuery", "pkgContent", 10, true)]
|
||||
[InlineData("depName", "dvcGroupId", "", "pkgContent", 10, true)]
|
||||
[InlineData("depName", "dvcGroupId", "dvcQuery", "", 10, true)]
|
||||
[InlineData("depName", "dvcGroupId", "dvcQuery", "pkgContent", -1, true)]
|
||||
public async Task PostDeploymentTest(string name, string deviceGroupId,
|
||||
string deviceGroupQuery, string packageContent,
|
||||
int priority, bool throwsException)
|
||||
{
|
||||
// Arrange
|
||||
var deploymentId = "test-deployment";
|
||||
this.deploymentsMock.Setup(x => x.CreateAsync(Match.Create<DeploymentServiceModel>(model =>
|
||||
model.DeviceGroupId == deviceGroupId &&
|
||||
model.PackageContent == packageContent &&
|
||||
model.Priority == priority &&
|
||||
model.Name == name &&
|
||||
model.Type == DeploymentType.EdgeManifest)))
|
||||
.ReturnsAsync(new DeploymentServiceModel()
|
||||
{
|
||||
Name = name,
|
||||
DeviceGroupId = deviceGroupId,
|
||||
DeviceGroupQuery = deviceGroupQuery,
|
||||
PackageContent = packageContent,
|
||||
Priority = priority,
|
||||
Id = deploymentId,
|
||||
Type = DeploymentType.EdgeManifest,
|
||||
CreatedDateTimeUtc = DateTime.UtcNow
|
||||
});
|
||||
|
||||
var depApiModel = new DeploymentApiModel()
|
||||
{
|
||||
Name = name,
|
||||
DeviceGroupId = deviceGroupId,
|
||||
DeviceGroupQuery = deviceGroupQuery,
|
||||
PackageContent = packageContent,
|
||||
Type = DeploymentType.EdgeManifest,
|
||||
Priority = priority
|
||||
};
|
||||
|
||||
// Act
|
||||
if (throwsException)
|
||||
{
|
||||
await Assert.ThrowsAsync<InvalidInputException>(async () => await this.deploymentsController.PostAsync(depApiModel));
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await this.deploymentsController.PostAsync(depApiModel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(deploymentId, result.DeploymentId);
|
||||
Assert.Equal(name, result.Name);
|
||||
Assert.Equal(deviceGroupId, result.DeviceGroupId);
|
||||
Assert.Equal(deviceGroupQuery, result.DeviceGroupQuery);
|
||||
Assert.Equal(packageContent, result.PackageContent);
|
||||
Assert.Equal(priority, result.Priority);
|
||||
Assert.Equal(DeploymentType.EdgeManifest, result.Type);
|
||||
Assert.True((DateTimeOffset.UtcNow - result.CreatedDateTimeUtc).TotalSeconds < 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Controllers;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WebService.Test.helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace WebService.Test.v1.Controllers
|
||||
{
|
||||
public class ModulesControllerTest
|
||||
{
|
||||
private readonly ModulesController modulesController;
|
||||
private readonly Mock<IDevices> devicesMock;
|
||||
private readonly HttpContext httpContext;
|
||||
private const string CONTINUATION_TOKEN_NAME = "x-ms-continuation";
|
||||
|
||||
public ModulesControllerTest()
|
||||
{
|
||||
this.devicesMock = new Mock<IDevices>();
|
||||
this.httpContext = new DefaultHttpContext();
|
||||
this.modulesController = new ModulesController(this.devicesMock.Object)
|
||||
{
|
||||
ControllerContext = new ControllerContext()
|
||||
{
|
||||
HttpContext = this.httpContext
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("", "", true)]
|
||||
[InlineData("deviceId", "", true)]
|
||||
[InlineData("", "moduleId", true)]
|
||||
[InlineData("deviceId", "moduleId", false)]
|
||||
public async Task GetSingleModuleTwinTest(string deviceId, string moduleId, bool throwsException)
|
||||
{
|
||||
if (throwsException)
|
||||
{
|
||||
await Assert.ThrowsAsync<InvalidInputException>(async () =>
|
||||
await this.modulesController.GetModuleTwinAsync(deviceId, moduleId));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arrange
|
||||
var twinResult = ModulesControllerTest.CreateTestTwin(deviceId, moduleId);
|
||||
this.devicesMock.Setup(x => x.GetModuleTwinAsync(deviceId, moduleId))
|
||||
.ReturnsAsync(twinResult);
|
||||
|
||||
// Act
|
||||
var module = await this.modulesController.GetModuleTwinAsync(deviceId, moduleId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(moduleId, module.ModuleId);
|
||||
Assert.Equal(deviceId, module.DeviceId);
|
||||
Assert.Equal("v2", module.Desired["version"]);
|
||||
Assert.Equal("v1", module.Reported["version"]);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
[InlineData("", "")]
|
||||
[InlineData("my module query", "continuationToken")]
|
||||
public async Task GetModuleTwinsTest(string query, string continuationToken)
|
||||
{
|
||||
const string resultToken = "nextToken";
|
||||
|
||||
var twinList = new List<TwinServiceModel>() {ModulesControllerTest.CreateTestTwin("d", "m")};
|
||||
var twins = new TwinServiceListModel(twinList, resultToken);
|
||||
|
||||
this.devicesMock.Setup(x => x.GetModuleTwinsByQueryAsync(query, continuationToken))
|
||||
.ReturnsAsync(twins);
|
||||
this.httpContext.Request.Headers.Add(CONTINUATION_TOKEN_NAME,
|
||||
new StringValues(continuationToken));
|
||||
|
||||
// Act
|
||||
var moduleTwins = await this.modulesController.GetModuleTwinsAsync(query);
|
||||
|
||||
// Assert
|
||||
var moduleTwin = moduleTwins.Items[0];
|
||||
Assert.Equal("d", moduleTwin.DeviceId);
|
||||
Assert.Equal("m", moduleTwin.ModuleId);
|
||||
Assert.Equal(resultToken, moduleTwins.ContinuationToken);
|
||||
Assert.Equal("v2", moduleTwin.Desired["version"]);
|
||||
Assert.Equal("v1", moduleTwin.Reported["version"]);
|
||||
}
|
||||
|
||||
private static TwinServiceModel CreateTestTwin(string deviceId, string moduleId)
|
||||
{
|
||||
return new TwinServiceModel()
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
ModuleId = moduleId,
|
||||
DesiredProperties = new Dictionary<string, JToken>()
|
||||
{
|
||||
{ "version", JToken.Parse("'v2'") }
|
||||
},
|
||||
ReportedProperties = new Dictionary<string, JToken>()
|
||||
{
|
||||
{ "version", JToken.Parse("'v1'") }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -79,7 +79,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.Runtime
|
|||
DevicePropertiesTTL = configData.GetInt(DEVICE_PROPERTIES_TTL_KEY),
|
||||
DevicePropertiesRebuildTimeout = configData.GetInt(DEVICE_PROPERTIES_REBUILD_TIMEOUT_KEY),
|
||||
StorageAdapterApiUrl = configData.GetString(STORAGE_ADAPTER_URL_KEY),
|
||||
UserManagementApiUrl = configData.GetString(USER_MANAGEMENT_URL_KEY),
|
||||
UserManagementApiUrl = configData.GetString(USER_MANAGEMENT_URL_KEY)
|
||||
};
|
||||
|
||||
this.ClientAuthConfig = new ClientAuthConfig
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Filters;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Controllers
|
||||
{
|
||||
[Route(Version.PATH + "/[controller]"), ExceptionsFilter]
|
||||
public class DeploymentsController : Controller
|
||||
{
|
||||
private readonly IDeployments deployments;
|
||||
|
||||
public DeploymentsController(IDeployments deployments)
|
||||
{
|
||||
this.deployments = deployments;
|
||||
}
|
||||
|
||||
/// <summary>Create a deployment targeting a particular group</summary>
|
||||
/// <param name="deployment">Deployment information</param>
|
||||
/// <returns>Deployment information and initial success metrics</returns>
|
||||
[HttpPost]
|
||||
[Authorize("CreateDeployments")]
|
||||
public async Task<DeploymentApiModel> PostAsync([FromBody] DeploymentApiModel deployment)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(deployment.Name))
|
||||
{
|
||||
throw new InvalidInputException("Name must be provided");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(deployment.DeviceGroupId))
|
||||
{
|
||||
throw new InvalidInputException("DeviceGroupId must be provided");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(deployment.DeviceGroupQuery))
|
||||
{
|
||||
throw new InvalidInputException("DeviceGroupQuery must be provided");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(deployment.PackageContent))
|
||||
{
|
||||
throw new InvalidInputException("PackageContent must be provided");
|
||||
}
|
||||
|
||||
if (deployment.Priority < 0)
|
||||
{
|
||||
throw new InvalidInputException($"Invalid priority provided of {deployment.Priority}. " +
|
||||
"It must be non-negative");
|
||||
}
|
||||
|
||||
return new DeploymentApiModel(await this.deployments.CreateAsync(deployment.ToServiceModel()));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<DeploymentListApiModel> GetAsync()
|
||||
{
|
||||
return new DeploymentListApiModel(await this.deployments.ListAsync());
|
||||
}
|
||||
|
||||
/// <summary>Get one deployment</summary>
|
||||
/// <param name="id">Deployment id</param>
|
||||
/// <param name="includeDeviceStatus">Whether to retrieve additional details regarding device status</param>
|
||||
/// <returns>Deployment information with metrics</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<DeploymentApiModel> GetAsync(string id, [FromQuery] bool includeDeviceStatus = false)
|
||||
{
|
||||
return new DeploymentApiModel(await this.deployments.GetAsync(id, includeDeviceStatus));
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize("DeleteDeployments")]
|
||||
public async Task DeleteAsync(string id)
|
||||
{
|
||||
await this.deployments.DeleteAsync(id);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Filters;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Controllers
|
||||
{
|
||||
[Route(Version.PATH + "/[controller]"), ExceptionsFilter]
|
||||
public class ModulesController : Controller
|
||||
{
|
||||
private const string CONTINUATION_TOKEN_NAME = "x-ms-continuation";
|
||||
private readonly IDevices devices;
|
||||
|
||||
public ModulesController(IDevices devices)
|
||||
{
|
||||
this.devices = devices;
|
||||
}
|
||||
|
||||
/// <summary>Retrieve module twin properties based on provided query</summary>
|
||||
/// <param name="query">Where clause of IoTHub query</param>
|
||||
/// <returns>List of module twins</returns>
|
||||
[HttpGet]
|
||||
public async Task<TwinPropertiesListApiModel> GetModuleTwinsAsync([FromQuery] string query)
|
||||
{
|
||||
string continuationToken = string.Empty;
|
||||
if (this.Request.Headers.ContainsKey(CONTINUATION_TOKEN_NAME))
|
||||
{
|
||||
continuationToken = this.Request.Headers[CONTINUATION_TOKEN_NAME].FirstOrDefault();
|
||||
}
|
||||
|
||||
return new TwinPropertiesListApiModel(
|
||||
await this.devices.GetModuleTwinsByQueryAsync(query, continuationToken));
|
||||
}
|
||||
|
||||
/// <summary>Retrieve module twin properties. Query in body of post request</summary>
|
||||
/// <param name="query">Where clause of IoTHub query</param>
|
||||
/// <returns>List of module twins</returns>
|
||||
[HttpPost("query")]
|
||||
public async Task<TwinPropertiesListApiModel> QueryModuleTwinsAsync([FromBody] string query)
|
||||
{
|
||||
return await this.GetModuleTwinsAsync(query);
|
||||
}
|
||||
|
||||
/// <summary>Get module information for a device</summary>
|
||||
/// <param name="deviceId">Device Id</param>
|
||||
/// <param name="moduleId">Module Id</param>
|
||||
/// <returns>Device information</returns>
|
||||
[HttpGet("{deviceId}/{moduleId}")]
|
||||
public async Task<TwinPropertiesApiModel> GetModuleTwinAsync(string deviceId, string moduleId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
throw new InvalidInputException("deviceId must be provided");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(moduleId))
|
||||
{
|
||||
throw new InvalidInputException("moduleId must be provided");
|
||||
}
|
||||
|
||||
var twin = await this.devices.GetModuleTwinAsync(deviceId, moduleId);
|
||||
return new TwinPropertiesApiModel(twin.DesiredProperties, twin.ReportedProperties,
|
||||
deviceId, moduleId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
||||
{
|
||||
public class DeploymentApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "Id")]
|
||||
public string DeploymentId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "Name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "CreatedDateTimeUtc")]
|
||||
public DateTime CreatedDateTimeUtc { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceGroupId")]
|
||||
public string DeviceGroupId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceGroupName")]
|
||||
public string DeviceGroupName { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceGroupQuery")]
|
||||
public string DeviceGroupQuery { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "PackageContent")]
|
||||
public string PackageContent { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "PackageName")]
|
||||
public string PackageName { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "Priority")]
|
||||
public int Priority { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty(PropertyName = "Type")]
|
||||
public DeploymentType Type { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "Metrics", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public DeploymentMetricsApiModel Metrics { get; set; }
|
||||
|
||||
public DeploymentApiModel()
|
||||
{
|
||||
}
|
||||
|
||||
public DeploymentApiModel(DeploymentServiceModel serviceModel)
|
||||
{
|
||||
this.CreatedDateTimeUtc = serviceModel.CreatedDateTimeUtc;
|
||||
this.DeploymentId = serviceModel.Id;
|
||||
this.DeviceGroupId = serviceModel.DeviceGroupId;
|
||||
this.DeviceGroupName = serviceModel.DeviceGroupName;
|
||||
this.DeviceGroupQuery = serviceModel.DeviceGroupQuery;
|
||||
this.Name = serviceModel.Name;
|
||||
this.PackageContent = serviceModel.PackageContent;
|
||||
this.PackageName = serviceModel.PackageName;
|
||||
this.Priority = serviceModel.Priority;
|
||||
this.Type = serviceModel.Type;
|
||||
this.Metrics = new DeploymentMetricsApiModel(serviceModel.DeploymentMetrics)
|
||||
{
|
||||
DeviceStatuses = serviceModel.DeploymentMetrics?.DeviceStatuses
|
||||
};
|
||||
}
|
||||
|
||||
public DeploymentApiModel(DeploymentServiceModel serviceModel,
|
||||
IDictionary<string, DeploymentStatus> deviceStatuses)
|
||||
{
|
||||
this.CreatedDateTimeUtc = serviceModel.CreatedDateTimeUtc;
|
||||
this.DeploymentId = serviceModel.Id;
|
||||
this.DeviceGroupId = serviceModel.DeviceGroupId;
|
||||
this.Name = serviceModel.Name;
|
||||
this.Priority = serviceModel.Priority;
|
||||
this.Type = serviceModel.Type;
|
||||
this.Metrics = new DeploymentMetricsApiModel(serviceModel.DeploymentMetrics)
|
||||
{
|
||||
DeviceStatuses = deviceStatuses
|
||||
};
|
||||
}
|
||||
|
||||
public DeploymentServiceModel ToServiceModel()
|
||||
{
|
||||
return new DeploymentServiceModel() {
|
||||
DeviceGroupId = this.DeviceGroupId,
|
||||
DeviceGroupName = this.DeviceGroupName,
|
||||
DeviceGroupQuery = this.DeviceGroupQuery,
|
||||
Name = this.Name,
|
||||
PackageContent = this.PackageContent,
|
||||
PackageName = this.PackageName,
|
||||
Priority = this.Priority,
|
||||
Type = this.Type
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
||||
{
|
||||
public class DeploymentListApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "Items")]
|
||||
public List<DeploymentApiModel> Items { get; set; }
|
||||
|
||||
public DeploymentListApiModel()
|
||||
{
|
||||
}
|
||||
|
||||
public DeploymentListApiModel(DeploymentServiceListModel deployments)
|
||||
{
|
||||
this.Items = new List<DeploymentApiModel>();
|
||||
deployments.Items.ForEach(deployment => this.Items.Add(new DeploymentApiModel(deployment)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
||||
{
|
||||
public class DeploymentMetricsApiModel
|
||||
{
|
||||
private const string APPLIED_METRICS_KEY = "appliedCount";
|
||||
private const string TARGETED_METRICS_KEY = "targetedCount";
|
||||
private const string SUCCESSFUL_METRICS_KEY = "reportedSuccessfulCount";
|
||||
private const string FAILED_METRICS_KEY = "reportedFailedCount";
|
||||
|
||||
[JsonProperty(PropertyName = "AppliedCount")]
|
||||
public long AppliedCount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "FailedCount")]
|
||||
public long FailedCount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "SucceededCount")]
|
||||
public long SucceededCount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "TargetedCount")]
|
||||
public long TargetedCount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceStatuses")]
|
||||
public IDictionary<string, DeploymentStatus> DeviceStatuses { get; set; }
|
||||
|
||||
public DeploymentMetricsApiModel()
|
||||
{
|
||||
}
|
||||
|
||||
public DeploymentMetricsApiModel(DeploymentMetrics metricsServiceModel)
|
||||
{
|
||||
if (metricsServiceModel == null) return;
|
||||
|
||||
var metrics = metricsServiceModel.Metrics;
|
||||
this.AppliedCount = metrics.TryGetValue(APPLIED_METRICS_KEY, out var value) ? value : 0;
|
||||
this.TargetedCount = metrics.TryGetValue(TARGETED_METRICS_KEY, out value) ? value : 0;
|
||||
this.SucceededCount = metrics.TryGetValue(SUCCESSFUL_METRICS_KEY, out value) ? value : 0;
|
||||
this.FailedCount = metrics.TryGetValue(FAILED_METRICS_KEY, out value) ? value : 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,9 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
|||
[JsonProperty(PropertyName = "Tags", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<string, JToken> Tags { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "IsEdgeDevice")]
|
||||
public bool IsEdgeDevice { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "IsSimulated", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool IsSimulated { get; set; }
|
||||
|
||||
|
@ -77,6 +80,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
|||
this.ETag = $"{this.ETag}|{device.Twin.ETag}";
|
||||
this.Properties = new TwinPropertiesApiModel(device.Twin.DesiredProperties, device.Twin.ReportedProperties);
|
||||
this.Tags = device.Twin.Tags;
|
||||
this.IsEdgeDevice = device.Twin.IsEdgeDevice;
|
||||
this.IsSimulated = device.Twin.IsSimulated;
|
||||
}
|
||||
}
|
||||
|
@ -117,14 +121,15 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
|||
|
||||
public DeviceServiceModel ToServiceModel()
|
||||
{
|
||||
var twinModel = new DeviceTwinServiceModel
|
||||
var twinModel = new TwinServiceModel
|
||||
(
|
||||
etag: this.TwinEtag,
|
||||
deviceId: this.Id,
|
||||
desiredProperties: this.Properties?.Desired,
|
||||
reportedProperties: this.Properties?.Reported,
|
||||
tags: this.Tags,
|
||||
isSimulated: this.IsSimulated
|
||||
isSimulated: this.IsSimulated,
|
||||
isEdgeDevice: this.IsEdgeDevice
|
||||
);
|
||||
|
||||
return new DeviceServiceModel
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
|||
this.Properties = new TwinPropertiesApiModel();
|
||||
}
|
||||
|
||||
public JobUpdateTwinApiModel(string deviceId, DeviceTwinServiceModel deviceTwin)
|
||||
public JobUpdateTwinApiModel(string deviceId, TwinServiceModel deviceTwin)
|
||||
{
|
||||
if (deviceTwin != null)
|
||||
{
|
||||
|
@ -42,9 +42,9 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
|||
}
|
||||
}
|
||||
|
||||
public DeviceTwinServiceModel ToServiceModel()
|
||||
public TwinServiceModel ToServiceModel()
|
||||
{
|
||||
return new DeviceTwinServiceModel
|
||||
return new TwinServiceModel
|
||||
(
|
||||
etag: this.ETag,
|
||||
deviceId: this.DeviceId,
|
||||
|
|
|
@ -14,16 +14,30 @@ namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
|||
[JsonProperty(PropertyName = "Desired", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<string, JToken> Desired { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "ModuleId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string ModuleId { get; set; }
|
||||
|
||||
public TwinPropertiesApiModel()
|
||||
{
|
||||
this.Reported = new Dictionary<string, JToken>();
|
||||
this.Desired = new Dictionary<string, JToken>();
|
||||
}
|
||||
|
||||
public TwinPropertiesApiModel(Dictionary<string, JToken> desired, Dictionary<string, JToken> reported)
|
||||
public TwinPropertiesApiModel(Dictionary<string, JToken> desired, Dictionary<string, JToken> reported) :
|
||||
this(desired, reported, string.Empty, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public TwinPropertiesApiModel(Dictionary<string, JToken> desired, Dictionary<string, JToken> reported,
|
||||
string deviceId, string moduleId)
|
||||
{
|
||||
this.Desired = desired;
|
||||
this.Reported = reported;
|
||||
this.DeviceId = deviceId;
|
||||
this.ModuleId = moduleId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.IotHubManager.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.IotHubManager.WebService.v1.Models
|
||||
{
|
||||
public class TwinPropertiesListApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "$metadata")]
|
||||
public Dictionary<string, string> Metadata => new Dictionary<string, string>
|
||||
{
|
||||
{ "$type", "DeviceList;" + Version.NUMBER },
|
||||
{ "$uri", "/" + Version.PATH + "/devices" }
|
||||
};
|
||||
|
||||
[JsonProperty(PropertyName = "ContinuationToken")]
|
||||
public string ContinuationToken { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "Items")]
|
||||
public List<TwinPropertiesApiModel> Items { get; set; }
|
||||
|
||||
public TwinPropertiesListApiModel()
|
||||
{
|
||||
}
|
||||
|
||||
public TwinPropertiesListApiModel(TwinServiceListModel twins)
|
||||
{
|
||||
this.Items = new List<TwinPropertiesApiModel>();
|
||||
this.ContinuationToken = twins.ContinuationToken;
|
||||
foreach (var t in twins.Items)
|
||||
{
|
||||
this.Items.Add(new TwinPropertiesApiModel(t.DesiredProperties, t.ReportedProperties,
|
||||
t.DeviceId, t.ModuleId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче