From 135b0a8a6db3cd53d2b3157420638c96d3d3ab8d Mon Sep 17 00:00:00 2001 From: Elvis Pranskevichus Date: Mon, 30 May 2022 18:50:39 -0700 Subject: [PATCH] Allow configuring the start of DCHP range Currently, DHCP, if enabled, uses the entire range of network addresses, sans network, gateway and broadcast. This makes life difficult for scenarios where some static address allocation is needed and is performed outside of Terraform configuration. Help this by allowing the configuration of the DHCP address range via the new `range_start_offset` attribute on the `dhcp` block. The default is `2`, which is equivalent to the current behavior of skipping the first two addresses. --- libvirt/helpers_test.go | 23 ++++++++++- libvirt/network.go | 51 +++++++++++++++++++++++- libvirt/resource_libvirt_network.go | 16 ++++++++ libvirt/resource_libvirt_network_test.go | 24 ++++++++++- website/docs/r/network.markdown | 27 ++++++++----- 5 files changed, 126 insertions(+), 15 deletions(-) diff --git a/libvirt/helpers_test.go b/libvirt/helpers_test.go index 099c65f2e..39abd4864 100644 --- a/libvirt/helpers_test.go +++ b/libvirt/helpers_test.go @@ -219,8 +219,13 @@ func testAccCheckDNSHosts(name string, expected []libvirtxml.NetworkDNSHost) res } } +type DHCPRange struct { + Start string + End string +} + // testAccCheckLibvirtNetworkDhcpStatus checks the expected DHCP status -func testAccCheckLibvirtNetworkDhcpStatus(name string, expectedDhcpStatus string) resource.TestCheckFunc { +func testAccCheckLibvirtNetworkDhcpStatus(name string, expectedDhcpStatus string, expectedRanges []DHCPRange) resource.TestCheckFunc { return func(s *terraform.State) error { virConn := testAccProvider.Meta().(*Client).libvirt networkDef, err := getNetworkDef(s, name, virConn) @@ -236,11 +241,25 @@ func testAccCheckLibvirtNetworkDhcpStatus(name string, expectedDhcpStatus string } } if expectedDhcpStatus == "enabled" { + var ranges []DHCPRange + for _, ips := range networkDef.IPs { if ips.DHCP == nil { - return fmt.Errorf("the network should have DHCP enabled") + continue + } + + for _, addrRange := range ips.DHCP.Ranges { + ranges = append(ranges, DHCPRange{addrRange.Start, addrRange.End}) } } + + if len(ranges) == 0 { + return fmt.Errorf("the network should have DHCP enabled") + } + + if !reflect.DeepEqual(ranges, expectedRanges) { + return fmt.Errorf("expected DHCP ranges: %v != actual DHCP ranges: %v", expectedRanges, ranges) + } } return nil } diff --git a/libvirt/network.go b/libvirt/network.go index a87ff94d1..e2919695b 100644 --- a/libvirt/network.go +++ b/libvirt/network.go @@ -1,8 +1,11 @@ package libvirt import ( + "encoding/binary" "fmt" "log" + "math" + "math/big" "net" "strings" @@ -61,8 +64,14 @@ func getIPsFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkIP, error) ipsPtrsLst := []libvirtxml.NetworkIP{} for num, addressI := range addresses.([]interface{}) { + dhcpOffsetKey := fmt.Sprintf("dhcp.%d.range_start_offset", num) + dhcpRangeStartOffsetVal, ok := d.GetOkExists(dhcpOffsetKey) + dhcpRangeStartOffset := 0 + if ok { + dhcpRangeStartOffset = dhcpRangeStartOffsetVal.(int) + } // get the IP address entry for this subnet (with a guessed DHCP range) - dni, dhcp, err := getNetworkIPConfig(addressI.(string)) + dni, dhcp, err := getNetworkIPConfig(addressI.(string), dhcpRangeStartOffset) if err != nil { return nil, err } @@ -87,7 +96,7 @@ func getIPsFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkIP, error) return ipsPtrsLst, nil } -func getNetworkIPConfig(address string) (*libvirtxml.NetworkIP, *libvirtxml.NetworkDHCP, error) { +func getNetworkIPConfig(address string, rangeStartOffset int) (*libvirtxml.NetworkIP, *libvirtxml.NetworkDHCP, error) { _, ipNet, err := net.ParseCIDR(address) if err != nil { return nil, nil, fmt.Errorf("error parsing addresses definition '%s': %s", address, err) @@ -119,6 +128,44 @@ func getNetworkIPConfig(address string) (*libvirtxml.NetworkIP, *libvirtxml.Netw start[len(start)-1]++ // then skip the .1 end[len(end)-1]-- // and skip the .255 (for broadcast) + rangeStartOffset -= 2 // we already skipped the first two addresses + if rangeStartOffset > 0 { + if len(start) == 4 { + startInt := binary.BigEndian.Uint32(start) + endInt := binary.BigEndian.Uint32(end) + + if startInt > math.MaxUint32-uint32(rangeStartOffset) { + return nil, nil, fmt.Errorf( + "specified DHCP range start offset is too large for the specified netmask") + } + + startInt += uint32(rangeStartOffset) + if startInt > endInt { + return nil, nil, fmt.Errorf( + "specified DHCP range start offset is too large for the specified netmask") + } + + binary.BigEndian.PutUint32(start, startInt) + } else if len(start) == 16 { + startInt := big.NewInt(0) + startInt.SetBytes(start) + endInt := big.NewInt(0) + endInt.SetBytes(end) + + startOffset := big.NewInt(int64(rangeStartOffset)) + + startInt = startInt.Add(startInt, startOffset) + if startInt.Cmp(endInt) > 0 { + return nil, nil, fmt.Errorf( + "specified DHCP range start offset is too large for the specified netmask") + } + + startInt.FillBytes(start) + } else { + return nil, nil, fmt.Errorf("unexpected IP address length: %d", len(start)) + } + } + dhcp := &libvirtxml.NetworkDHCP{ Ranges: []libvirtxml.NetworkDHCPRange{ { diff --git a/libvirt/resource_libvirt_network.go b/libvirt/resource_libvirt_network.go index f1d2ae2a7..6d37275f5 100644 --- a/libvirt/resource_libvirt_network.go +++ b/libvirt/resource_libvirt_network.go @@ -220,6 +220,22 @@ func resourceLibvirtNetwork() *schema.Resource { Optional: true, Required: false, }, + "range_start_offset": { + Type: schema.TypeInt, + Default: 0, + Optional: true, + Required: false, + ValidateFunc: func(v interface{}, k string) ([]string, []error) { + i := v.(int) + if i < 2 || i > 65535 { + return nil, []error{ + fmt.Errorf("%s: must be between 2 and 65535, inclusive, got %v", k, v), + } + } else { + return nil, nil + } + }, + }, }, }, }, diff --git a/libvirt/resource_libvirt_network_test.go b/libvirt/resource_libvirt_network_test.go index 78d269186..c6d20f0a9 100644 --- a/libvirt/resource_libvirt_network_test.go +++ b/libvirt/resource_libvirt_network_test.go @@ -412,11 +412,31 @@ func TestAccLibvirtNetwork_DhcpEnabled(t *testing.T) { addresses = ["10.17.3.0/24"] dhcp { enabled = true + range_start_offset = 16 } }`, randomNetworkResource, randomNetworkName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dhcp.0.enabled", "true"), - testAccCheckLibvirtNetworkDhcpStatus("libvirt_network."+randomNetworkResource, "enabled"), + resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dhcp.0.range_start_offset", "16"), + testAccCheckLibvirtNetworkDhcpStatus("libvirt_network."+randomNetworkResource, "enabled", []DHCPRange{{"10.17.3.16", "10.17.3.254"}}), + ), + }, + { + Config: fmt.Sprintf(` + resource "libvirt_network" "%s" { + name = "%s" + mode = "nat" + domain = "k8s.local" + addresses = ["2001:db8:ca2:2::/64"] + dhcp { + enabled = true + range_start_offset = 65530 + } + }`, randomNetworkResource, randomNetworkName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dhcp.0.enabled", "true"), + resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dhcp.0.range_start_offset", "65530"), + testAccCheckLibvirtNetworkDhcpStatus("libvirt_network."+randomNetworkResource, "enabled", []DHCPRange{{"2001:db8:ca2:2::fffa", "2001:db8:ca2:2::fffe"}}), ), }, }, @@ -446,7 +466,7 @@ func TestAccLibvirtNetwork_DhcpDisabled(t *testing.T) { }`, randomNetworkResource, randomNetworkName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dhcp.0.enabled", "false"), - testAccCheckLibvirtNetworkDhcpStatus("libvirt_network."+randomNetworkResource, "disabled"), + testAccCheckLibvirtNetworkDhcpStatus("libvirt_network."+randomNetworkResource, "disabled", nil), ), }, }, diff --git a/website/docs/r/network.markdown b/website/docs/r/network.markdown index ecadce40f..532f6f0f4 100644 --- a/website/docs/r/network.markdown +++ b/website/docs/r/network.markdown @@ -187,18 +187,27 @@ resource "libvirt_network" "k8snet" { } ``` -* `dhcp` - (Optional) DHCP configuration. +* `dhcp` - (Optional) DHCP configuration. You need to use it in conjuction with the adresses variable. * `enabled` - (Optional) when false, disable the DHCP server + * `range_start_offset` - (Optional) a non-negative integer offset from the + start of subnet(s) defined by `addresses` to use as the DHCP range. + Defaults to `2` and cannot be less than `2` (first address is the network + address and the second is the gateway address) or greater than `65535` + (maximum number of addresses supported by libvirt in a subnet). + ```hcl - resource "libvirt_network" "test_net" { - name = "networktest" - mode = "nat" - domain = "k8s.local" - addresses = ["10.17.3.0/24"] - dhcp { - enabled = true - } + resource "libvirt_network" "test_net" { + name = "networktest" + mode = "nat" + domain = "k8s.local" + addresses = ["10.17.3.0/24"] + dhcp { + enabled = true + # Omit the first /28 from the DHCP range, i.e. use + # 10.17.3.16 - 10.17.3.254 + range_start_offset = 16 + } ``` * `dnsmasq_options` - (Optional) configuration of Dnsmasq options for the network