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