Functional tests (#2333)

* more tests

* revert compute, since "released" version has bug when creating scaleset extensions

* format

* found bug, i think...

* now should be fixed

Co-authored-by: stas <statis@microsoft.com>
This commit is contained in:
Stas 2022-09-02 08:16:48 -07:00 committed by GitHub
parent c54db04083
commit ee1ad0abd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 276 additions and 174 deletions

View File

@ -24,7 +24,7 @@
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.3.0" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.6.0" />
<PackageReference Include="Azure.Data.Tables" Version="12.5.0" />
<PackageReference Include="Azure.ResourceManager.Compute" Version="1.0.0" />
<PackageReference Include="Azure.ResourceManager.Compute" Version="1.0.0-beta.8" />
<PackageReference Include="Azure.Core" Version="1.25.0" />
<PackageReference Include="Azure.Identity" Version="1.6.0" />
<PackageReference Include="Azure.Messaging.EventGrid" Version="4.10.0" />

View File

@ -149,7 +149,7 @@ public class Proxy {
return await _context.RequestHandling.NotOk(
req,
request.ErrorV,
"debug_proxy delet");
"debug_proxy delete");
}
var regions = await _context.ProxyForwardOperations.RemoveForward(

View File

@ -64,14 +64,16 @@ public class TimerProxy {
// since we do not support bring your own NSG
if (await nsgOpertions.GetNsg(region) != null) {
var network = await Network.Create(region, _context);
var network = await Network.Init(region, _context);
var subnet = await network.GetSubnet();
var vnet = await network.GetVnet();
if (subnet != null && vnet != null) {
var result = await nsgOpertions.AssociateSubnet(region, vnet, subnet);
if (!result.OkV) {
_logger.Error($"Failed to associate NSG and subnet due to {result.ErrorV} in region {region}");
if (subnet != null) {
var vnet = await network.GetVnet();
if (vnet != null) {
var result = await nsgOpertions.AssociateSubnet(region, vnet, subnet);
if (!result.OkV) {
_logger.Error($"Failed to associate NSG and subnet due to {result.ErrorV} in region {region}");
}
}
}
}

View File

@ -64,47 +64,55 @@ public class ServiceConfiguration : IServiceConfig {
#endif
}
private static string? GetEnv(string name) {
var v = Environment.GetEnvironmentVariable(name);
if (String.IsNullOrEmpty(v))
return null;
return v;
}
//TODO: Add environment variable to control where to write logs to
public LogDestination[] LogDestinations { get; set; }
//TODO: Get this from Environment variable
public ApplicationInsights.DataContracts.SeverityLevel LogSeverityLevel => ApplicationInsights.DataContracts.SeverityLevel.Verbose;
public string? ApplicationInsightsAppId => Environment.GetEnvironmentVariable("APPINSIGHTS_APPID");
public string? ApplicationInsightsInstrumentationKey => Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
public string? ApplicationInsightsAppId => GetEnv("APPINSIGHTS_APPID");
public string? ApplicationInsightsInstrumentationKey => GetEnv("APPINSIGHTS_INSTRUMENTATIONKEY");
public string? AzureSignalRConnectionString => Environment.GetEnvironmentVariable("AzureSignalRConnectionString");
public string? AzureSignalRServiceTransportType => Environment.GetEnvironmentVariable("AzureSignalRServiceTransportType");
public string? AzureSignalRConnectionString => GetEnv("AzureSignalRConnectionString");
public string? AzureSignalRServiceTransportType => GetEnv("AzureSignalRServiceTransportType");
public string? AzureWebJobDisableHomePage { get => Environment.GetEnvironmentVariable("AzureWebJobsDisableHomepage"); }
public string? AzureWebJobStorage { get => Environment.GetEnvironmentVariable("AzureWebJobsStorage"); }
public string? AzureWebJobDisableHomePage { get => GetEnv("AzureWebJobsDisableHomepage"); }
public string? AzureWebJobStorage { get => GetEnv("AzureWebJobsStorage"); }
public string? DiagnosticsAzureBlobContainerSasUrl { get => Environment.GetEnvironmentVariable("DIAGNOSTICS_AZUREBLOBCONTAINERSASURL"); }
public string? DiagnosticsAzureBlobRetentionDays { get => Environment.GetEnvironmentVariable("DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS"); }
public string? DiagnosticsAzureBlobContainerSasUrl { get => GetEnv("DIAGNOSTICS_AZUREBLOBCONTAINERSASURL"); }
public string? DiagnosticsAzureBlobRetentionDays { get => GetEnv("DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS"); }
public string? MultiTenantDomain { get => Environment.GetEnvironmentVariable("MULTI_TENANT_DOMAIN"); }
public string? MultiTenantDomain { get => GetEnv("MULTI_TENANT_DOMAIN"); }
public string? OneFuzzDataStorage { get => Environment.GetEnvironmentVariable("ONEFUZZ_DATA_STORAGE"); }
public string? OneFuzzFuncStorage { get => Environment.GetEnvironmentVariable("ONEFUZZ_FUNC_STORAGE"); }
public string? OneFuzzInstance { get => Environment.GetEnvironmentVariable("ONEFUZZ_INSTANCE"); }
public string? OneFuzzInstanceName { get => Environment.GetEnvironmentVariable("ONEFUZZ_INSTANCE_NAME"); }
public string? OneFuzzKeyvault { get => Environment.GetEnvironmentVariable("ONEFUZZ_KEYVAULT"); }
public string? OneFuzzMonitor { get => Environment.GetEnvironmentVariable("ONEFUZZ_MONITOR"); }
public string? OneFuzzOwner { get => Environment.GetEnvironmentVariable("ONEFUZZ_OWNER"); }
public string? OneFuzzResourceGroup { get => Environment.GetEnvironmentVariable("ONEFUZZ_RESOURCE_GROUP"); }
public string? OneFuzzTelemetry { get => Environment.GetEnvironmentVariable("ONEFUZZ_TELEMETRY"); }
public string? OneFuzzDataStorage { get => GetEnv("ONEFUZZ_DATA_STORAGE"); }
public string? OneFuzzFuncStorage { get => GetEnv("ONEFUZZ_FUNC_STORAGE"); }
public string? OneFuzzInstance { get => GetEnv("ONEFUZZ_INSTANCE"); }
public string? OneFuzzInstanceName { get => GetEnv("ONEFUZZ_INSTANCE_NAME"); }
public string? OneFuzzKeyvault { get => GetEnv("ONEFUZZ_KEYVAULT"); }
public string? OneFuzzMonitor { get => GetEnv("ONEFUZZ_MONITOR"); }
public string? OneFuzzOwner { get => GetEnv("ONEFUZZ_OWNER"); }
public string? OneFuzzResourceGroup { get => GetEnv("ONEFUZZ_RESOURCE_GROUP"); }
public string? OneFuzzTelemetry { get => GetEnv("ONEFUZZ_TELEMETRY"); }
public string OneFuzzVersion {
get {
// version can be overridden by config:
return Environment.GetEnvironmentVariable("ONEFUZZ_VERSION")
return GetEnv("ONEFUZZ_VERSION")
?? _oneFuzzVersion
?? throw new InvalidOperationException("Unable to read OneFuzz version from assembly");
}
}
public string? OneFuzzAllowOutdatedAgent => Environment.GetEnvironmentVariable("ONEFUZZ_ALLOW_OUTDATED_AGENT");
public string? OneFuzzAllowOutdatedAgent => GetEnv("ONEFUZZ_ALLOW_OUTDATED_AGENT");
public string OneFuzzNodeDisposalStrategy { get => Environment.GetEnvironmentVariable("ONEFUZZ_NODE_DISPOSAL_STRATEGY") ?? "scale_in"; }
public string OneFuzzNodeDisposalStrategy { get => GetEnv("ONEFUZZ_NODE_DISPOSAL_STRATEGY") ?? "scale_in"; }
public string OneFuzzStoragePrefix => ""; // in production we never prefix the tables
}

View File

@ -122,7 +122,7 @@ public sealed class Creds : ICreds {
var resource = await uid.GetAsync();
var principalId = resource.Value.Data.Properties.ToObjectFromJson<ScaleSetIdentity>().principalId;
return new Guid(principalId);
return Guid.Parse(principalId);
});
}

View File

@ -5,7 +5,7 @@ using Azure.ResourceManager.Compute;
namespace Microsoft.OneFuzz.Service;
public interface IDiskOperations {
DiskImageCollection ListDisks(string resourceGroup);
DiskCollection ListDisks(string resourceGroup);
Async.Task<bool> DeleteDisk(string resourceGroup, string name);
}
@ -23,7 +23,7 @@ public class DiskOperations : IDiskOperations {
public async Task<bool> DeleteDisk(string resourceGroup, string name) {
try {
_logTracer.Info($"deleting disks {resourceGroup} : {name}");
var disk = await _creds.GetResourceGroupResource().GetDiskImageAsync(name);
var disk = await _creds.GetResourceGroupResource().GetDiskAsync(name);
if (disk != null) {
await disk.Value.DeleteAsync(WaitUntil.Started);
return true;
@ -35,8 +35,8 @@ public class DiskOperations : IDiskOperations {
return false;
}
public DiskImageCollection ListDisks(string resourceGroup) {
public DiskCollection ListDisks(string resourceGroup) {
_logTracer.Info($"listing disks {resourceGroup}");
return _creds.GetResourceGroupResource().GetDiskImages();
return _creds.GetResourceGroupResource().GetDisks();
}
}

View File

@ -226,7 +226,8 @@ public class Extensions : IExtensions {
);
var fileName = $"{pool.Name}/config.json";
await _context.Containers.SaveBlob(new Container("vm-scripts"), fileName, (JsonSerializer.Serialize(config, EntityConverter.GetJsonSerializerOptions())), StorageType.Config);
var configJson = JsonSerializer.Serialize(config, EntityConverter.GetJsonSerializerOptions());
await _context.Containers.SaveBlob(new Container("vm-scripts"), fileName, configJson, StorageType.Config);
return await ConfigUrl(new Container("vm-scripts"), fileName, false);
}
@ -264,6 +265,7 @@ public class Extensions : IExtensions {
await UpdateManagedScripts();
var urlsUpdated = urls ?? new();
var managedIdentity = JsonSerializer.Serialize(new { ManagedIdentity = new Dictionary<string, string>() }, _extensionSerializerOptions);
if (vmOs == Os.Windows) {
var vmScripts = await ConfigUrl(new Container("vm-scripts"), "managed.ps1", withSas) ?? throw new Exception("failed to get VmScripts config url");
var toolsAzCopy = await ConfigUrl(new Container("tools"), "win64/azcopy.exe", withSas) ?? throw new Exception("failed to get toolsAzCopy config url");
@ -286,7 +288,7 @@ public class Extensions : IExtensions {
TypeHandlerVersion = "1.9",
AutoUpgradeMinorVersion = true,
Settings = new BinaryData(JsonSerializer.Serialize(new { commandToExecute = toExecuteCmd, fileUris = urlsUpdated }, _extensionSerializerOptions)),
ProtectedSettings = new BinaryData(JsonSerializer.Serialize(new { managedIdentity = new Dictionary<string, string>() }, _extensionSerializerOptions))
ProtectedSettings = new BinaryData(managedIdentity)
};
return extension;
} else if (vmOs == Os.Linux) {
@ -301,7 +303,6 @@ public class Extensions : IExtensions {
var toExecuteCmd = $"sh setup.sh {mode.ToString().ToLowerInvariant()}";
var extensionSettings = JsonSerializer.Serialize(new { CommandToExecute = toExecuteCmd, FileUris = urlsUpdated }, _extensionSerializerOptions);
var protectedExtensionSettings = JsonSerializer.Serialize(new { ManagedIdentity = new Dictionary<string, string>() }, _extensionSerializerOptions);
var extension = new VMExtensionWrapper {
Name = "CustomScript",
@ -312,7 +313,7 @@ public class Extensions : IExtensions {
ForceUpdateTag = Guid.NewGuid().ToString(),
AutoUpgradeMinorVersion = true,
Settings = new BinaryData(extensionSettings),
ProtectedSettings = new BinaryData(protectedExtensionSettings)
ProtectedSettings = new BinaryData(managedIdentity)
};
return extension;
}

View File

@ -65,7 +65,7 @@ public class ImageOperations : IImageOperations {
}
} else {
try {
name = (await _context.Creds.GetResourceGroupResource().GetDiskImages().GetAsync(
name = (await _context.Creds.GetResourceGroupResource().GetImages().GetAsync(
parsed.Data.Name
)).Value.Data.StorageProfile.OSDisk.OSType.ToString().ToLowerInvariant();
} catch (Exception ex) when (
@ -96,15 +96,13 @@ public class ImageOperations : IImageOperations {
version = imageInfo.Version;
}
var vmImage = await subscription.GetVirtualMachineImageAsync(
region,
imageInfo.Publisher,
imageInfo.Offer,
imageInfo.Sku
, version
);
name = vmImage.Value.OSDiskImageOperatingSystem!.Value.ToString().ToLower();
name = (await subscription.GetVirtualMachineImageAsync(
region,
imageInfo.Publisher,
imageInfo.Offer,
imageInfo.Sku
, version
)).Value.OSDiskImageOperatingSystem.ToString().ToLower();
} catch (RequestFailedException ex) {
return OneFuzzResult<Os>.Error(
ErrorCode.INVALID_IMAGE,

View File

@ -16,6 +16,8 @@ public interface IIpOperations {
public Async.Task<string?> GetPublicIp(ResourceIdentifier resourceId);
public Async.Task<string?> GetPublicIp(string resourceId);
public Async.Task<PublicIPAddressResource?> GetIp(string resourceGroup, string name);
public Async.Task DeleteNic(string resourceGroup, string name);
@ -86,6 +88,9 @@ public class IpOperations : IIpOperations {
var ips = await _networkInterfaceQuery.ListInstancePrivateIps(scalesetId, instance.OkV);
return ips.FirstOrDefault();
}
public async Task<string?> GetPublicIp(string resourceId) {
return await GetPublicIp(new ResourceIdentifier(resourceId));
}
public async Task<string?> GetPublicIp(ResourceIdentifier resourceId) {
// TODO: Parts of this function seem redundant, but I'm mirroring
@ -117,12 +122,15 @@ public class IpOperations : IIpOperations {
public async Task<OneFuzzResultVoid> CreatePublicNic(string resourceGroup, string name, string region, Nsg? nsg) {
_logTracer.Info($"creating nic for {resourceGroup}:{name} in {region}");
var network = await Network.Create(region, _context);
var network = await Network.Init(region, _context);
var subnetId = await network.GetId();
if (subnetId is null) {
await network.Create();
return OneFuzzResultVoid.Ok;
var r = await network.Create();
if (!r.IsOk) {
_logTracer.Error($"failed to create network in region {region} due to {r.ErrorV}");
}
return r;
}
if (nsg != null) {

View File

@ -22,7 +22,7 @@ public class Network {
_networkConfig = networkConfig;
}
public static async Async.Task<Network> Create(string region, IOnefuzzContext context) {
public static async Async.Task<Network> Init(string region, IOnefuzzContext context) {
var group = context.Creds.GetBaseResourceGroup();
var instanceConfig = await context.ConfigOperations.Fetch();
var networkConfig = instanceConfig.NetworkConfig;

View File

@ -217,7 +217,7 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IPr
}
foreach (var status in instanceView.Statuses) {
if (status.Level == ComputeStatusLevelType.Error) {
if (status.Level == StatusLevelTypes.Error) {
yield return $"code:{status.Code} status:{status.DisplayStatus} message:{status.Message}";
}
}

View File

@ -198,7 +198,7 @@ public class ScalesetOperations : StatefulOrm<Scaleset, ScalesetState, ScalesetO
//# This was done as part of the generated per-task setup script.
_logTracer.Info($"{SCALESET_LOG_PREFIX} setup. scalset_id: {scaleset.ScalesetId}");
var network = await Network.Create(scaleset.Region, _context);
var network = await Network.Init(scaleset.Region, _context);
var networkId = await network.GetId();
if (networkId is null) {
_logTracer.Info($"{SCALESET_LOG_PREFIX} creating network. region: {scaleset.Region} scaleset_id:{scaleset.ScalesetId}");

View File

@ -68,12 +68,17 @@ public class Subnet : ISubnet {
}
public async Async.Task<SubnetResource?> GetSubnet(string vnetName, string subnetName) {
var vnet = await this.GetVnet(vnetName);
try {
var vnet = await this.GetVnet(vnetName);
if (vnet != null) {
return await vnet.GetSubnetAsync(subnetName);
if (vnet != null) {
return await vnet.GetSubnetAsync(subnetName);
}
return null;
} catch (RequestFailedException ex) when (ex.Status == 404) {
return null;
}
return null;
}
public async Task<ResourceIdentifier?> GetSubnetId(string name, string subnetName) {

View File

@ -1,6 +1,7 @@
using Azure.Core;
using Azure.ResourceManager.Compute;
namespace Microsoft.OneFuzz.Service {
public class VMExtensionWrapper {
public AzureLocation? Location { get; init; }
@ -27,7 +28,7 @@ namespace Microsoft.OneFuzz.Service {
var protectedSettings = ProtectedSettings ?? new BinaryData(new Dictionary<string, string>());
return (Name!, new VirtualMachineExtensionData(Location.Value) {
ExtensionType = TypePropertiesType,
TypePropertiesType = TypePropertiesType,
Publisher = Publisher,
TypeHandlerVersion = TypeHandlerVersion,
AutoUpgradeMinorVersion = AutoUpgradeMinorVersion,
@ -49,7 +50,8 @@ namespace Microsoft.OneFuzz.Service {
var protectedSettings = ProtectedSettings ?? new BinaryData(new Dictionary<string, string>());
return new VirtualMachineScaleSetExtensionData() {
ExtensionType = TypePropertiesType,
Name = Name,
TypePropertiesType = TypePropertiesType,
Publisher = Publisher,
TypeHandlerVersion = TypeHandlerVersion,
AutoUpgradeMinorVersion = AutoUpgradeMinorVersion,
@ -62,3 +64,4 @@ namespace Microsoft.OneFuzz.Service {
}
}

View File

@ -1,6 +1,5 @@
using System.Threading.Tasks;
using Azure;
using Azure.Core;
using Azure.ResourceManager.Compute;
using Azure.ResourceManager.Compute.Models;
using Newtonsoft.Json;
@ -68,7 +67,7 @@ public class VmOperations : IVmOperations {
public async Task<VirtualMachineData?> GetVm(string name) {
// _logTracer.Debug($"getting vm: {name}");
try {
var result = await _context.Creds.GetResourceGroupResource().GetVirtualMachineAsync(name, InstanceViewType.InstanceView);
var result = await _context.Creds.GetResourceGroupResource().GetVirtualMachineAsync(name, InstanceViewTypes.InstanceView);
if (result == null) {
return null;
}
@ -256,20 +255,20 @@ public class VmOperations : IVmOperations {
}
var vmParams = new VirtualMachineData(location) {
OSProfile = new VirtualMachineOSProfile {
OSProfile = new OSProfile {
ComputerName = "node",
AdminUsername = "onefuzz",
},
HardwareProfile = new VirtualMachineHardwareProfile {
HardwareProfile = new HardwareProfile {
VmSize = vmSku,
},
StorageProfile = new VirtualMachineStorageProfile {
StorageProfile = new StorageProfile {
ImageReference = GenerateImageReference(image),
},
NetworkProfile = new VirtualMachineNetworkProfile(),
NetworkProfile = new NetworkProfile(),
};
vmParams.NetworkProfile.NetworkInterfaces.Add(new VirtualMachineNetworkInterfaceReference { Id = nic.Id });
vmParams.NetworkProfile.NetworkInterfaces.Add(new NetworkInterfaceReference { Id = nic.Id });
var imageOs = await _context.ImageOperations.GetOs(location, image);
if (!imageOs.IsOk) {
@ -286,7 +285,7 @@ public class VmOperations : IVmOperations {
DisablePasswordAuthentication = true,
};
vmParams.OSProfile.LinuxConfiguration.SshPublicKeys.Add(
new SshPublicKeyConfiguration {
new SshPublicKeyInfo {
Path = "/home/onefuzz/.ssh/authorized_keys",
KeyData = sshPublicKey
}
@ -333,7 +332,7 @@ public class VmOperations : IVmOperations {
var imageRef = new ImageReference();
if (image.StartsWith("/", StringComparison.Ordinal)) {
imageRef.Id = new ResourceIdentifier(image);
imageRef.Id = image;
} else {
var imageVal = image.Split(":", 4);
imageRef.Publisher = imageVal[0];

View File

@ -262,15 +262,15 @@ public class VmssOperations : IVmssOperations {
Sku = new ComputeSku() { Name = vmSku, Capacity = vmCount },
Overprovision = false,
SinglePlacementGroup = false,
UpgradePolicy = new VirtualMachineScaleSetUpgradePolicy() { Mode = VirtualMachineScaleSetUpgradeMode.Manual },
UpgradePolicy = new UpgradePolicy() { Mode = UpgradeMode.Manual },
Identity = new ManagedServiceIdentity(managedServiceIdentityType: ManagedServiceIdentityType.UserAssigned),
};
vmssData.Identity.UserAssignedIdentities.Add(_creds.GetScalesetIdentityResourcePath(), new UserAssignedIdentity());
vmssData.VirtualMachineProfile = new VirtualMachineScaleSetVmProfile() { Priority = VirtualMachinePriorityType.Regular };
vmssData.VirtualMachineProfile = new VirtualMachineScaleSetVmProfile() { Priority = VirtualMachinePriorityTypes.Regular };
var imageRef = new ImageReference();
if (image.StartsWith('/')) {
imageRef.Id = new ResourceIdentifier(image);
imageRef.Id = image;
} else {
var info = IImageOperations.GetImageInfo(image);
imageRef.Publisher = info.Publisher;
@ -303,7 +303,7 @@ public class VmssOperations : IVmssOperations {
case Os.Linux:
vmssData.VirtualMachineProfile.OSProfile.LinuxConfiguration = new LinuxConfiguration();
vmssData.VirtualMachineProfile.OSProfile.LinuxConfiguration.DisablePasswordAuthentication = true;
var i = new SshPublicKeyConfiguration() { KeyData = sshPublicKey, Path = "/home/onefuzz/.ssh/authorized_keys" };
var i = new SshPublicKeyInfo() { KeyData = sshPublicKey, Path = "/home/onefuzz/.ssh/authorized_keys" };
vmssData.VirtualMachineProfile.OSProfile.LinuxConfiguration.SshPublicKeys.Add(i);
break;
default:
@ -311,10 +311,10 @@ public class VmssOperations : IVmssOperations {
}
if (ephemeralOsDisks) {
vmssData.VirtualMachineProfile.StorageProfile.OSDisk = new VirtualMachineScaleSetOSDisk(DiskCreateOptionType.FromImage);
vmssData.VirtualMachineProfile.StorageProfile.OSDisk = new VirtualMachineScaleSetOSDisk(DiskCreateOptionTypes.FromImage);
vmssData.VirtualMachineProfile.StorageProfile.OSDisk.DiffDiskSettings = new DiffDiskSettings();
vmssData.VirtualMachineProfile.StorageProfile.OSDisk.DiffDiskSettings.Option = DiffDiskOption.Local;
vmssData.VirtualMachineProfile.StorageProfile.OSDisk.Caching = CachingType.ReadOnly;
vmssData.VirtualMachineProfile.StorageProfile.OSDisk.DiffDiskSettings.Option = DiffDiskOptions.Local;
vmssData.VirtualMachineProfile.StorageProfile.OSDisk.Caching = CachingTypes.ReadOnly;
}
if (spotInstance.HasValue && spotInstance.Value) {
@ -323,8 +323,8 @@ public class VmssOperations : IVmssOperations {
//
// https://docs.microsoft.com/en-us/azure/
// virtual-machine-scale-sets/use-spot#resource-manager-templates
vmssData.VirtualMachineProfile.EvictionPolicy = VirtualMachineEvictionPolicyType.Deallocate;
vmssData.VirtualMachineProfile.Priority = VirtualMachinePriorityType.Spot;
vmssData.VirtualMachineProfile.EvictionPolicy = VirtualMachineEvictionPolicyTypes.Deallocate;
vmssData.VirtualMachineProfile.Priority = VirtualMachinePriorityTypes.Spot;
vmssData.VirtualMachineProfile.BillingMaxPrice = 1.0;
}
@ -370,14 +370,14 @@ public class VmssOperations : IVmssOperations {
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
var sub = _creds.GetSubscriptionResource();
var skus = sub.GetComputeResourceSkusAsync(filter: TableClient.CreateQueryFilter($"location eq '{region}'"));
var skus = sub.GetResourceSkusAsync(filter: TableClient.CreateQueryFilter($"location eq '{region}'"));
var skuNames = new List<string>();
await foreach (var sku in skus) {
var available = true;
if (sku.Restrictions is not null) {
foreach (var restriction in sku.Restrictions) {
if (restriction.RestrictionsType == ComputeResourceSkuRestrictionsType.Location &&
if (restriction.RestrictionsType == ResourceSkuRestrictionsType.Location &&
restriction.Values.Contains(region, StringComparer.OrdinalIgnoreCase)) {
available = false;
break;

View File

@ -65,12 +65,12 @@
},
"Azure.ResourceManager.Compute": {
"type": "Direct",
"requested": "[1.0.0, )",
"resolved": "1.0.0",
"contentHash": "dmDhokNFcyreIYZMkha8OsLmch0hW/sdOA2nxN4MPVd8KJgGWB8DxCZWwG7qhh59RInKtmWOP8BnBS5mN+W5wQ==",
"requested": "[1.0.0-beta.8, )",
"resolved": "1.0.0-beta.8",
"contentHash": "rYYjjmEdmcOa8O4UgO/bdJ/qQclNZjuHdalxRJ0AhUHCORcM1f1BbIKR9CoN83IpfuEE+X+n5XY9QZcKvfrGVA==",
"dependencies": {
"Azure.Core": "1.25.0",
"Azure.ResourceManager": "1.2.0",
"Azure.Core": "1.24.0",
"Azure.ResourceManager": "1.0.0",
"System.Text.Json": "4.7.2"
}
},

View File

@ -5,11 +5,11 @@ using Xunit.Abstractions;
namespace FunctionalTests;
interface IFromJsonElement<T> {
public interface IFromJsonElement<T> {
T Convert(JsonElement e);
}
class BooleanResult : IFromJsonElement<BooleanResult> {
public class BooleanResult : IFromJsonElement<BooleanResult> {
JsonElement _e;
public BooleanResult() { }
public BooleanResult(JsonElement e) => _e = e;
@ -19,7 +19,7 @@ class BooleanResult : IFromJsonElement<BooleanResult> {
public BooleanResult Convert(JsonElement e) => new BooleanResult(e);
}
abstract class ApiBase {
public abstract class ApiBase {
Uri _endpoint;
Microsoft.OneFuzz.Service.Request _request;

View File

@ -3,7 +3,7 @@ using Xunit;
namespace FunctionalTests;
class Error : IComparable<Error>, IFromJsonElement<Error> {
public class Error : IComparable<Error>, IFromJsonElement<Error> {
JsonElement _e;
public Error(JsonElement e) {
@ -21,18 +21,18 @@ class Error : IComparable<Error>, IFromJsonElement<Error> {
return res.ValueKind == JsonValueKind.Object && res.TryGetProperty("code", out _) && res.TryGetProperty("errors", out _);
}
public int CompareTo(Error? error) {
if (error is null) {
public int CompareTo(Error? other) {
if (other is null) {
return -1;
}
var sameErrorMessages = Errors.Count() == error.Errors.Count();
foreach (var s in error.Errors) {
var sameErrorMessages = Errors.Count() == other.Errors.Count();
foreach (var s in other.Errors) {
if (!sameErrorMessages) break;
sameErrorMessages = Errors.Contains(s);
}
if (error.Code == this.Code && sameErrorMessages) {
if (other.Code == this.Code && sameErrorMessages) {
return 0;
} else
return 1;

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace FunctionalTests {
public class Helpers {
public static async Task<(Pool, Scaleset)> CreatePoolAndScaleset(PoolApi poolApi, ScalesetApi scalesetApi, string os = "linux", string? region = null, int numNodes = 2) {
var image = (os == "linux") ? ScalesetApi.Image_Ubuntu_20_04 : ScalesetApi.ImageWindows;
var newPoolId = Guid.NewGuid().ToString();
var newPoolName = PoolApi.TestPoolPrefix + newPoolId;
var newPool = await poolApi.Create(newPoolName, os);
Assert.True(newPool.IsOk, $"failed to create new pool: {newPool.ErrorV}");
var newScalesetResult = await scalesetApi.Create(newPool.OkV!.Name, numNodes, region: region, image: image);
Assert.True(newScalesetResult.IsOk, $"failed to crate new scaleset: {newScalesetResult.ErrorV}");
var newScaleset = newScalesetResult.OkV!;
return (newPool.OkV!, newScaleset);
}
}
}

View File

@ -5,7 +5,7 @@ using Xunit.Abstractions;
namespace FunctionalTests;
class Node : IFromJsonElement<Node> {
public class Node : IFromJsonElement<Node> {
JsonElement _e;
public Node() { }
@ -35,19 +35,19 @@ class Node : IFromJsonElement<Node> {
}
class NodeApi : ApiBase {
public class NodeApi : ApiBase {
public NodeApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) :
base(endpoint, "/api/Node", request, output) {
}
public async Task<JsonElement> Update(Guid machineId, bool? debugKeepNode = null) {
public async Task<BooleanResult> Update(Guid machineId, bool? debugKeepNode = null) {
var j = new JsonObject();
if (debugKeepNode is not null)
j.Add("debug_keep_node", JsonValue.Create(debugKeepNode));
j.Add("machine_id", JsonValue.Create(machineId));
return await Post(j);
return Return<BooleanResult>(await Post(j));
}
public async Task<Result<IEnumerable<Node>, Error>> Get(Guid? machineId = null, List<string>? state = null, Guid? scalesetId = null, string? poolName = null) {
var j = new JsonObject();
@ -66,10 +66,10 @@ class NodeApi : ApiBase {
return IEnumerableResult<Node>(await Get(j));
}
public async Task<JsonElement> Patch(Guid machineId) {
public async Task<BooleanResult> Patch(Guid machineId) {
var j = new JsonObject();
j.Add("machine_id", JsonValue.Create(machineId));
return await Patch(j);
return Return<BooleanResult>(await Patch(j));
}
public async Task<BooleanResult> Delete(Guid machineId) {

View File

@ -5,7 +5,7 @@ using Xunit.Abstractions;
namespace FunctionalTests;
class Pool : IFromJsonElement<Pool> {
public class Pool : IFromJsonElement<Pool> {
JsonElement _e;
@ -19,26 +19,30 @@ class Pool : IFromJsonElement<Pool> {
public Pool Convert(JsonElement e) => new Pool(e);
}
class PoolApi : ApiBase {
public class PoolApi : ApiBase {
public static string TestPoolPrefix = "FT-DELETE-";
public const string TestPoolPrefix = "FT-DELETE-";
public PoolApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) :
base(endpoint, "/api/Pool", request, output) {
}
public async Task<BooleanResult> Delete(string name, bool now = true) {
_output.WriteLine($"deleting pool: {name}, now: {now}");
var root = new JsonObject();
root.Add("name", JsonValue.Create(name));
root.Add("now", JsonValue.Create(now));
return Return<BooleanResult>(await Delete(root));
}
public async Task<Result<IEnumerable<Pool>, Error>> Get(string? poolName = null, string? poolId = null, string? state = null) {
public async Task<Result<IEnumerable<Pool>, Error>> Get(string? name = null, string? id = null, string? state = null) {
var root = new JsonObject();
root.Add("pool_id", poolId);
root.Add("name", poolName);
root.Add("state", state);
if (id is not null)
root.Add("pool_id", id);
if (name is not null)
root.Add("name", name);
if (state is not null)
root.Add("state", state);
var res = await Get(root);
return IEnumerableResult<Pool>(res);
@ -52,7 +56,6 @@ class PoolApi : ApiBase {
foreach (var pool in pools.OkV) {
if (pool.Name.StartsWith(TestPoolPrefix)) {
_output.WriteLine($"Deleting {pool.Name}");
var deleted = await Delete(pool.Name);
Assert.True(deleted.Result);
}
@ -60,6 +63,8 @@ class PoolApi : ApiBase {
}
public async Task<Result<Pool, Error>> Create(string poolName, string os, string arch = "x86_64") {
_output.WriteLine($"creating new pool {poolName} os: {os}");
var rootPoolCreate = new JsonObject();
rootPoolCreate.Add("name", poolName);
rootPoolCreate.Add("os", os);

View File

@ -3,9 +3,7 @@ using System.Text.Json.Nodes;
using Xunit.Abstractions;
namespace FunctionalTests;
class Proxy : IFromJsonElement<Proxy> {
public class Proxy : IFromJsonElement<Proxy> {
JsonElement _e;
public Proxy() { }
@ -19,8 +17,7 @@ class Proxy : IFromJsonElement<Proxy> {
public Proxy Convert(JsonElement e) => new Proxy(e);
}
class Forward : IFromJsonElement<Forward>, IComparable<Forward> {
public class Forward : IFromJsonElement<Forward>, IComparable<Forward> {
JsonElement _e;
public Forward() { }
public Forward(JsonElement e) => _e = e;
@ -43,14 +40,27 @@ class Forward : IFromJsonElement<Forward>, IComparable<Forward> {
}
}
class ProxyGetResult : IFromJsonElement<ProxyGetResult>, IComparable<ProxyGetResult> {
public class ProxyGetResult : IFromJsonElement<ProxyGetResult>, IComparable<ProxyGetResult> {
JsonElement _e;
public ProxyGetResult() { }
public ProxyGetResult(JsonElement e) => _e = e;
public string? Ip => _e.ValueKind == JsonValueKind.Null ? null : _e.GetProperty("ip").GetString();
public string? Ip {
get {
JsonElement ip;
if (_e.TryGetProperty("ip", out ip)) {
if (ip.ValueKind == JsonValueKind.Null) {
return null;
} else {
return ip.GetString();
}
} else {
return null;
}
}
}
public Forward Forward => new Forward(_e.GetProperty("forward"));
@ -76,7 +86,7 @@ class ProxyGetResult : IFromJsonElement<ProxyGetResult>, IComparable<ProxyGetRes
}
class ProxyApi : ApiBase {
public class ProxyApi : ApiBase {
public ProxyApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) :
base(endpoint, "/api/proxy", request, output) {
@ -84,9 +94,12 @@ class ProxyApi : ApiBase {
public async Task<Result<IEnumerable<Proxy>, Error>> Get(Guid? scalesetId = null, Guid? machineId = null, int? dstPort = null) {
var root = new JsonObject();
root.Add("scaleset_id", scalesetId);
root.Add("machine_id", machineId);
root.Add("dst_port", dstPort);
if (scalesetId is not null)
root.Add("scaleset_id", scalesetId);
if (machineId is not null)
root.Add("machine_id", machineId);
if (dstPort is not null)
root.Add("dst_port", dstPort);
var r = await Get(root);
if (Error.IsError(r)) {

View File

@ -4,10 +4,7 @@ using Xunit;
using Xunit.Abstractions;
namespace FunctionalTests;
class Authentication {
public class Authentication {
JsonElement _e;
public Authentication() { }
@ -20,7 +17,7 @@ class Authentication {
}
class ScalesetNodeState {
public class ScalesetNodeState {
JsonElement _e;
public ScalesetNodeState() { }
@ -32,7 +29,7 @@ class ScalesetNodeState {
public string? NodeState => _e.GetProperty("state").GetString();
}
class Scaleset : IFromJsonElement<Scaleset> {
public class Scaleset : IFromJsonElement<Scaleset> {
JsonElement _e;
public Scaleset() { }
public Scaleset(JsonElement e) => _e = e;
@ -68,9 +65,10 @@ class Scaleset : IFromJsonElement<Scaleset> {
public Scaleset Convert(JsonElement e) => new Scaleset(e);
}
class ScalesetApi : ApiBase {
public class ScalesetApi : ApiBase {
public const string Image_Ubuntu_20_04 = "Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest";
public const string ImageWindows = "MicrosoftWindowsDesktop:Windows-10:win10-21h2-pro:latest";
public ScalesetApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) :
base(endpoint, "/api/Scaleset", request, output) { }
@ -78,20 +76,26 @@ class ScalesetApi : ApiBase {
public async Task<Result<IEnumerable<Scaleset>, Error>> Get(Guid? id = null, string? state = null, bool? includeAuth = false) {
var root = new JsonObject();
root.Add("scaleset_id", id);
root.Add("state", state);
root.Add("include_auth", includeAuth);
if (id is not null)
root.Add("scaleset_id", id);
if (state is not null)
root.Add("state", state);
if (includeAuth is not null)
root.Add("include_auth", includeAuth);
var res = await Get(root);
return IEnumerableResult<Scaleset>(res);
}
public async Task<Result<Scaleset, Error>> Create(string poolName, int size, string vmSku = "Standard_D2s_v3", string image = Image_Ubuntu_20_04, bool spotInstance = false) {
public async Task<Result<Scaleset, Error>> Create(string poolName, int size, string? region = null, string vmSku = "Standard_D2s_v3", string image = Image_Ubuntu_20_04, bool spotInstance = false) {
_output.WriteLine($"Creating scaleset in pool {poolName}, size: {size}");
var rootScalesetCreate = new JsonObject();
rootScalesetCreate.Add("pool_name", poolName);
rootScalesetCreate.Add("vm_sku", vmSku);
rootScalesetCreate.Add("image", image);
rootScalesetCreate.Add("size", size);
rootScalesetCreate.Add("spot_instance", spotInstance);
rootScalesetCreate.Add("spot_instances", spotInstance);
if (region is not null)
rootScalesetCreate.Add("region", region);
var tags = new JsonObject();
tags.Add("Purpose", "Functional-Test");

View File

@ -6,3 +6,6 @@
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0005:Using directive is unnecessary.", Justification = "Test code")]
[assembly: SuppressMessage("Design", "CA1036:Override methods on comparable types", Justification = "Test code", Scope = "type", Target = "~T:FunctionalTests.Error")]
[assembly: SuppressMessage("Design", "CA1036:Override methods on comparable types", Justification = "Test code", Scope = "type", Target = "~T:FunctionalTests.Forward")]
[assembly: SuppressMessage("Design", "CA1036:Override methods on comparable types", Justification = "Test code", Scope = "type", Target = "~T:FunctionalTests.ProxyGetResult")]

View File

@ -8,11 +8,15 @@ namespace FunctionalTests {
public class TestNode {
NodeApi _nodeApi;
ScalesetApi _scalesetApi;
PoolApi _poolApi;
private readonly ITestOutputHelper _output;
public TestNode(ITestOutputHelper output) {
_output = output;
_nodeApi = new NodeApi(ApiClient.Endpoint, ApiClient.Request, output);
_scalesetApi = new ScalesetApi(ApiClient.Endpoint, ApiClient.Request, output);
_poolApi = new PoolApi(ApiClient.Endpoint, ApiClient.Request, output);
}
[Fact]
@ -34,5 +38,32 @@ namespace FunctionalTests {
}
[Fact]
async Task GetPatchPostDelete() {
var (pool, scaleset) = await Helpers.CreatePoolAndScaleset(_poolApi, _scalesetApi, "linux");
scaleset = await _scalesetApi.WaitWhile(scaleset.ScalesetId, sc => sc.State == "init" || sc.State == "setup");
Assert.True(scaleset.Nodes!.Count > 0);
var nodeState = scaleset.Nodes!.First();
var nodeResult = await _nodeApi.Get(nodeState.MachineId);
Assert.True(nodeResult.IsOk, $"failed to get node due to {nodeResult.ErrorV}");
var node = nodeResult.OkV!.First();
node = await _nodeApi.WaitWhile(node.MachineId, n => n.State == "init" || n.State == "setup");
var r = await _nodeApi.Patch(node.MachineId);
Assert.True(r.Result);
var rr = await _nodeApi.Update(node.MachineId, false);
var d = await _nodeApi.Delete(node.MachineId);
Assert.True(d.Result);
var deletePool = await _poolApi.Delete(pool.Name);
Assert.True(deletePool.Result);
}
}
}

View File

@ -19,7 +19,7 @@ namespace FunctionalTests {
[Fact]
async Task GetNonExistentPool() {
var p = await _poolApi.Get(poolName: Guid.NewGuid().ToString());
var p = await _poolApi.Get(name: Guid.NewGuid().ToString());
Assert.True(p.ErrorV!.UnableToFindPoolError);
}

View File

@ -37,16 +37,7 @@ namespace FunctionalTests {
[Fact]
public async Task CreateResetDelete() {
var newPoolId = Guid.NewGuid().ToString();
var newPoolName = PoolApi.TestPoolPrefix + newPoolId;
var newPool = await _poolApi.Create(newPoolName, "linux");
Assert.True(newPool.IsOk, $"failed to create new pool: {newPool.ErrorV}");
var newScalesetResult = await _scalesetApi.Create(newPool.OkV!.Name, 2);
Assert.True(newScalesetResult.IsOk, $"failed to crate new scaleset: {newScalesetResult.ErrorV}");
var newScaleset = newScalesetResult.OkV!;
var (newPool, newScaleset) = await Helpers.CreatePoolAndScaleset(_poolApi, _scalesetApi, "linux");
newScaleset = await _scalesetApi.WaitWhile(newScaleset.ScalesetId, sc => sc.State == "init" || sc.State == "setup");
@ -81,12 +72,10 @@ namespace FunctionalTests {
_output.WriteLine($"deleted proxy");
var deletePool = await _poolApi.Delete(newPoolName);
var deletePool = await _poolApi.Delete(newPool.Name);
Assert.True(deletePool.Result);
_output.WriteLine($"deleted pool {newPoolName}");
_output.WriteLine($"deleted pool {newPool.Name}");
}
}
}

View File

@ -34,18 +34,11 @@ namespace FunctionalTests {
}
}
[Fact]
public async Task CreateAndDelete() {
var newPoolId = Guid.NewGuid().ToString();
var newPoolName = PoolApi.TestPoolPrefix + newPoolId;
var newPool = await _poolApi.Create(newPoolName, "linux");
public async Task CreateAndDelete(string os) {
var (newPool, newScaleset) = await Helpers.CreatePoolAndScaleset(_poolApi, _scalesetApi, os);
Assert.True(newPool.IsOk, $"failed to create new pool: {newPool.ErrorV}");
var newScalesetResult = await _scalesetApi.Create(newPool.OkV!.Name, 2);
Assert.True(newScalesetResult.IsOk, $"failed to crate new scaleset: {newScalesetResult.ErrorV}");
var newScaleset = newScalesetResult.OkV!;
var newScalesetResultAgain = await _scalesetApi.Create(newPool.Name, 2);
var newScalesetResultAgainAgain = await _scalesetApi.Create(newPool.Name, 5);
try {
_output.WriteLine($"New scale set info id: {newScaleset.ScalesetId}, pool: {newScaleset.PoolName}, state: {newScaleset.State}, error: {newScaleset.Error}");
@ -56,7 +49,7 @@ namespace FunctionalTests {
var poolsCreated = await _poolApi.Get();
Assert.True(poolsCreated.IsOk, $"failed to get pools: {poolsCreated.ErrorV}");
var newPools = poolsCreated.OkV!.Where(p => p.Name == newPoolName);
var newPools = poolsCreated.OkV!.Where(p => p.Name == newPool.Name);
var newScalesets = scalesetsCreated.OkV!.Where(sc => sc.ScalesetId == newScaleset.ScalesetId);
Assert.True(newPools.Count() == 1);
@ -94,31 +87,43 @@ namespace FunctionalTests {
}
} finally {
var preDeleteScalesets = await _scalesetApi.Get();
var deletedPoolResult = await _poolApi.Delete(newPoolName);
var deletedPoolResult = await _poolApi.Delete(newPool.Name);
Assert.True(preDeleteScalesets.IsOk, $"failed to get pre-deleted scalesets due to: {preDeleteScalesets.ErrorV}");
var preDelete = preDeleteScalesets.OkV!.Where(sc => sc.PoolName == newPoolName);
Assert.True(preDelete.Count() == 1);
var preDelete = preDeleteScalesets.OkV!.Where(sc => sc.PoolName == newPool.Name);
Assert.True(preDelete.Count() == 3);
Result<IEnumerable<Pool>, Error> deletedPool;
do {
await Task.Delay(TimeSpan.FromSeconds(10.0));
deletedPool = await _poolApi.Get(newPoolName);
deletedPool = await _poolApi.Get(newPool.Name);
} while (deletedPool.IsOk);
Assert.True(deletedPool.ErrorV!.UnableToFindPoolError);
var postDeleteScalesets = await _scalesetApi.Get();
Assert.True(postDeleteScalesets.IsOk, $"failed to get scalesets after finishing pool deletion due to {postDeleteScalesets.ErrorV}");
_output.WriteLine($"Pool is deleted {newPoolName}");
_output.WriteLine($"Pool is deleted {newPool.Name}");
var postDelete = postDeleteScalesets.OkV!.Where(sc => sc.PoolName == newPoolName);
var postDelete = postDeleteScalesets.OkV!.Where(sc => sc.PoolName == newPool.Name);
Assert.False(postDelete.Any());
var patch1 = await _scalesetApi.Patch(newScaleset.ScalesetId, 1);
Assert.False(patch1.IsOk);
Assert.True(patch1.ErrorV!.UnableToFindScalesetError);
}
return;
}
[Fact]
public async Task CreateAndDeleteLinux() {
await CreateAndDelete("linux");
}
[Fact]
public async Task CreateAndDeleteWindows() {
await CreateAndDelete("windows");
}
}
}

View File

@ -103,11 +103,11 @@
},
"Azure.ResourceManager.Compute": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "dmDhokNFcyreIYZMkha8OsLmch0hW/sdOA2nxN4MPVd8KJgGWB8DxCZWwG7qhh59RInKtmWOP8BnBS5mN+W5wQ==",
"resolved": "1.0.0-beta.8",
"contentHash": "rYYjjmEdmcOa8O4UgO/bdJ/qQclNZjuHdalxRJ0AhUHCORcM1f1BbIKR9CoN83IpfuEE+X+n5XY9QZcKvfrGVA==",
"dependencies": {
"Azure.Core": "1.25.0",
"Azure.ResourceManager": "1.2.0",
"Azure.Core": "1.24.0",
"Azure.ResourceManager": "1.0.0",
"System.Text.Json": "4.7.2"
}
},
@ -2153,7 +2153,7 @@
"Azure.Identity": "1.6.0",
"Azure.Messaging.EventGrid": "4.10.0",
"Azure.ResourceManager": "1.3.1",
"Azure.ResourceManager.Compute": "1.0.0",
"Azure.ResourceManager.Compute": "[1.0.0-beta.8, )",
"Azure.ResourceManager.Monitor": "1.0.0-beta.2",
"Azure.ResourceManager.Network": "1.0.0",
"Azure.ResourceManager.Resources": "1.3.0",

View File

@ -131,11 +131,11 @@
},
"Azure.ResourceManager.Compute": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "dmDhokNFcyreIYZMkha8OsLmch0hW/sdOA2nxN4MPVd8KJgGWB8DxCZWwG7qhh59RInKtmWOP8BnBS5mN+W5wQ==",
"resolved": "1.0.0-beta.8",
"contentHash": "rYYjjmEdmcOa8O4UgO/bdJ/qQclNZjuHdalxRJ0AhUHCORcM1f1BbIKR9CoN83IpfuEE+X+n5XY9QZcKvfrGVA==",
"dependencies": {
"Azure.Core": "1.25.0",
"Azure.ResourceManager": "1.2.0",
"Azure.Core": "1.24.0",
"Azure.ResourceManager": "1.0.0",
"System.Text.Json": "4.7.2"
}
},
@ -2297,7 +2297,7 @@
"Azure.Identity": "1.6.0",
"Azure.Messaging.EventGrid": "4.10.0",
"Azure.ResourceManager": "1.3.1",
"Azure.ResourceManager.Compute": "1.0.0",
"Azure.ResourceManager.Compute": "[1.0.0-beta.8, )",
"Azure.ResourceManager.Monitor": "1.0.0-beta.2",
"Azure.ResourceManager.Network": "1.0.0",
"Azure.ResourceManager.Resources": "1.3.0",