From 560fcdf2344f92cf5c55c0a38546dddbfae2afdb Mon Sep 17 00:00:00 2001 From: Dave Curylo Date: Wed, 8 Feb 2023 23:09:13 -0500 Subject: [PATCH] VM support for IP forwarding and accelerated networking. --- RELEASE_NOTES.md | 3 + .../api-overview/resources/virtual-machine.md | 8 +- src/Farmer/Arm/Network.fs | 23 +++++ src/Farmer/Builders/Builders.Vm.fs | 30 +++++++ src/Farmer/Types.fs | 1 + src/Tests/VirtualMachine.fs | 85 +++++++++++++++++++ 6 files changed, 147 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index dc8b4c702..fc95873e2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,9 @@ Release Notes ============= +## vNext +* Virtual Machines: Accelerated networking and IP forwarding for interfaces + ## 1.7.15 * Adds the West US 3 location * Virtual Machines: Support for multiple network interfaces on a VM diff --git a/docs/content/api-overview/resources/virtual-machine.md b/docs/content/api-overview/resources/virtual-machine.md index 4d1378154..dd69c0dc1 100644 --- a/docs/content/api-overview/resources/virtual-machine.md +++ b/docs/content/api-overview/resources/virtual-machine.md @@ -42,14 +42,16 @@ In addition, every VM you create will add a SecureString parameter to the ARM te |custom_script_files|Uploads the supplied set of files, specified by URI, to the VM on creation.| |aad_ssh_login|Adds the `AADSSHLoginForLinux` extension on Linux VM's (requires `system_identity`).| |custom_data|Sets the custom data field for the VM.| -|public_ip|Specifies or removes the public IP for this VM| -|ip_allocation|Sets the public IP as Dynamic or Static. Default is Dynamic.| |disable_password_authentication|Disables password authentication on the VM. Must include at least one key if true| |add_authorized_key|adds one authorized key| |add_authorized_keys|adds a list of authorized keys| |add_identity|Adds a managed identity to the Virtual Machine.| |system_identity|Activates the system identity of the Virtual Machine.| -|private_ip_allocation| Sets the private ip as Dynamic or Static default is dynamic.| +|public_ip|Specifies or removes the public IP for this VM| +|ip_allocation|Sets the *public* IP as Dynamic or Static. Default is dynamic.| +|private_ip_allocation| Sets the *private* IP as Dynamic or Static. Default is dynamic.| +|ip_forwarding|Enable or disable IP forwarding on the primary network interface. Secondary NICs will leave it undefined.| +|accelerated_networking|Enable or disable accelerated networking on all network interfaces generated for the VM.| |add_ip_configuration| Add `ipConfig` definitions to add additional IP addresses or connect to multiple subnets. Connecting to additional subnets will generate a NIC for each subnet. | |network_security_group| Sets the Network Security Group (NSG) for VM/NIC. Enables you to create and share firewall rule sets.| |link_to_network_security_group| Specify an existing Network Security Group (NSG) for VM/NIC. | diff --git a/src/Farmer/Arm/Network.fs b/src/Farmer/Arm/Network.fs index 33679f1ab..82034ae7a 100644 --- a/src/Farmer/Arm/Network.fs +++ b/src/Farmer/Arm/Network.fs @@ -558,10 +558,31 @@ type IpConfiguration = Primary: bool option } +module NetworkInterface = + open Vm + + /// Accelerated networking only supported on certain VM sizes. + let (|AcceleratedNetworkingSupported|AcceleratedNetworkingUnsupported|) (vmSize: VMSize) = + let size = vmSize.ArmValue + + if size.Contains "_A" || size.Contains "_NC" || size.Contains "_NV" then + AcceleratedNetworkingUnsupported + else + match vmSize with + | Standard_B1ls + | Standard_B1ms + | Standard_B1s + | Standard_B2s + | Standard_B4ms + | Standard_B8ms -> AcceleratedNetworkingUnsupported // failwithf "Accelerated networking unsupported for specified VM size. Using '%s'." state.Size.ArmValue + | _ -> AcceleratedNetworkingSupported + type NetworkInterface = { Name: ResourceName Location: Location + EnableAcceleratedNetworking: bool option + EnableIpForwarding: bool option IpConfigs: IpConfiguration list VirtualNetwork: LinkedResource NetworkSecurityGroup: ResourceId option @@ -594,6 +615,8 @@ type NetworkInterface = let props = {| primary = this.Primary |> Option.map box |> Option.toObj + enableAcceleratedNetworking = this.EnableAcceleratedNetworking |> Option.map box |> Option.toObj + enableIPForwarding = this.EnableIpForwarding |> Option.map box |> Option.toObj ipConfigurations = this.IpConfigs |> List.mapi (fun index ipConfig -> diff --git a/src/Farmer/Builders/Builders.Vm.fs b/src/Farmer/Builders/Builders.Vm.fs index a07684ec6..fd38ce141 100644 --- a/src/Farmer/Builders/Builders.Vm.fs +++ b/src/Farmer/Builders/Builders.Vm.fs @@ -3,6 +3,7 @@ module Farmer.Builders.VirtualMachine open Farmer open Farmer.Arm +open Farmer.FeatureFlag open Farmer.PublicIpAddress open Farmer.PrivateIpAddress open Farmer.Vm @@ -48,6 +49,8 @@ type VmConfig = Subnet: AutoGeneratedResource PublicIp: ResourceRef option IpAllocation: PublicIpAddress.AllocationMethod option + AcceleratedNetworking: FeatureFlag option + IpForwarding: FeatureFlag option IpConfigs: IpConfiguration list PrivateIpAllocation: PrivateIpAddress.AllocationMethod option LoadBalancerBackendAddressPools: LinkedResource list @@ -116,6 +119,12 @@ type VmConfig = else ResourceName $"{this.NicName.Name.Value}-{subnetName.Value}" Location = location + EnableAcceleratedNetworking = this.AcceleratedNetworking |> Option.map toBool + EnableIpForwarding = // IP forwarding optionally enabled on primary NIC only. + if isPrimaryNic then + this.IpForwarding |> Option.map toBool + else + None IpConfigs = subnetIpConfigs Primary = if ipConfigsBySubnet.Length > 1 then // multiple NICs, need to indicate the primary @@ -314,6 +323,8 @@ type VirtualMachineBuilder() = Subnet = Derived(fun config -> config.DeriveResourceName subnets "subnet") PublicIp = automaticPublicIp IpAllocation = None + AcceleratedNetworking = None + IpForwarding = None IpConfigs = [] PrivateIpAllocation = None LoadBalancerBackendAddressPools = [] @@ -322,6 +333,14 @@ type VirtualMachineBuilder() = } member _.Run(state: VmConfig) = + match state.AcceleratedNetworking with + | Some (Enabled) -> + match state.Size with + | NetworkInterface.AcceleratedNetworkingUnsupported -> + raiseFarmer $"Accelerated networking unsupported for specified VM size '{state.Size.ArmValue}'." + | NetworkInterface.AcceleratedNetworkingSupported -> () + | _ -> () + { state with DataDisks = state.DataDisks @@ -492,6 +511,17 @@ type VirtualMachineBuilder() = member this.SubnetName(state: VmConfig, name) = this.SubnetName(state, ResourceName name) + /// Control accelerated networking for the VM network interfaces + [] + member _.AcceleratedNetworking(state: VmConfig, flag: FeatureFlag) = + { state with + AcceleratedNetworking = Some flag + } + + /// Enable or disable IP forwarding on the primary VM network interface. + [] + member _.IpForwarding(state: VmConfig, flag: FeatureFlag) = { state with IpForwarding = Some flag } + /// Uses an external VNet instead of creating a new one. [] member _.LinkToVNet(state: VmConfig, name: ResourceName) = diff --git a/src/Farmer/Types.fs b/src/Farmer/Types.fs index b88754761..47d3fd1a4 100644 --- a/src/Farmer/Types.fs +++ b/src/Farmer/Types.fs @@ -427,6 +427,7 @@ type FeatureFlag = module FeatureFlag = let ofBool enabled = if enabled then Enabled else Disabled + let toBool (flag: FeatureFlag) = flag.AsBoolean let invert flag = match flag with diff --git a/src/Tests/VirtualMachine.fs b/src/Tests/VirtualMachine.fs index b4015c70c..6fd45d3e2 100644 --- a/src/Tests/VirtualMachine.fs +++ b/src/Tests/VirtualMachine.fs @@ -632,6 +632,91 @@ let tests = Expect.equal (secondNicIp.ToString()) "192.168.12.13" "Static IP is wrong or missing" } + test "IP forwarding set for first NIC only" { + let deployment = + arm { + add_resources + [ + vm { + name "foo" + username "foo" + ip_forwarding Enabled + + add_ip_configurations [ ipConfig { subnet_name (ResourceName "another-subnet") } ] + } + ] + } + + let jobj = Newtonsoft.Json.Linq.JObject.Parse(deployment.Template |> Writer.toJson) + let firstNicProps = jobj.SelectToken("resources[?(@.name=='foo-nic')].properties") + + Expect.equal + firstNicProps.["enableIPForwarding"] + (JValue true) + "First NIC should have IP forwarding enabled" + + let secondNicProps = + jobj.SelectToken("resources[?(@.name=='foo-nic-another-subnet')].properties") + + Expect.isNull secondNicProps.["enableIPForwarding"] "Second NIC should not have IP forwarding" + } + + test "Accelerated networking set for all NICs" { + let deployment = + arm { + add_resources + [ + vm { + name "foo" + username "foo" + vm_size Standard_D2s_v5 + accelerated_networking Enabled + + add_ip_configurations [ ipConfig { subnet_name (ResourceName "another-subnet") } ] + } + ] + } + + let jobj = Newtonsoft.Json.Linq.JObject.Parse(deployment.Template |> Writer.toJson) + let firstNicProps = jobj.SelectToken("resources[?(@.name=='foo-nic')].properties") + + Expect.equal + firstNicProps.["enableAcceleratedNetworking"] + (JValue true) + "First NIC should have accelerated networking enabled" + + let secondNicProps = + jobj.SelectToken("resources[?(@.name=='foo-nic-another-subnet')].properties") + + Expect.equal + secondNicProps.["enableAcceleratedNetworking"] + (JValue true) + "Second NIC should have accelerated networking enabled" + } + + test "Accelerated networking not allowed on A-series VM" { + Expect.throws + (fun _ -> + let _ = + arm { + add_resources + [ + vm { + name "foo" + username "foo" + vm_size Basic_A0 + accelerated_networking Enabled + + add_ip_configurations + [ ipConfig { subnet_name (ResourceName "another-subnet") } ] + } + ] + } + + ()) + "Expected failure using accelerated networking with default VM size." + } + test "Can attach to NSG" { let vmName = "fooVm" let myNsg = nsg { name "testNsg" }