Skip to content

Commit

Permalink
More configuration options for networks
Browse files Browse the repository at this point in the history
  • Loading branch information
inercia committed Mar 14, 2018
1 parent d37e941 commit ce90234
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 90 deletions.
2 changes: 1 addition & 1 deletion libvirt/resource_libvirt_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1238,7 +1238,7 @@ func setNetworkInterfaces(d *schema.ResourceData, domainDef *libvirtxml.Domain,
mac = strings.ToUpper(macI.(string))
} else {
var err error
mac, err = RandomMACAddress()
mac, err = randomMACAddress()
if err != nil {
return fmt.Errorf("Error generating mac address: %s", err)
}
Expand Down
274 changes: 219 additions & 55 deletions libvirt/resource_libvirt_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,83 @@ func resourceLibvirtNetwork() *schema.Resource {
Optional: true,
Required: false,
},
"dns_forwarder": {
"dhcp": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"address": {
Type: schema.TypeString,
"enabled": {
Type: schema.TypeBool,
Default: true,
Optional: true,
Required: false,
ForceNew: true,
},
"domain": {
Type: schema.TypeString,
},
},
},
"dns": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Default: true,
Optional: true,
Required: false,
},
"local_only": {
Type: schema.TypeBool,
Default: false,
Optional: true,
Required: false,
},
"forwarders": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"host": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"address": {
Type: schema.TypeString,
Optional: true,
Required: false,
ForceNew: true,
},
"name": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
},
},
},
},
"routes": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
Expand Down Expand Up @@ -167,8 +223,17 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro

networkDef := newNetworkDef()
networkDef.Name = d.Get("name").(string)
networkDef.Domain = &libvirtxml.NetworkDomain{
Name: d.Get("domain").(string),

if domain, ok := d.GetOk("domain"); ok {
networkDef.Domain = &libvirtxml.NetworkDomain{
Name: domain.(string),
}

if dnsLocalOnly, ok := d.GetOk("dns.0.local_only"); ok {
if dnsLocalOnly.(bool) {
networkDef.Domain.LocalOnly = "yes" // this "boolean" must be "yes"|"no"
}
}
}

// use a bridge provided by the user, or create one otherwise (libvirt will assign on automatically when empty)
Expand All @@ -195,77 +260,128 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro
networkDef.Forward.NAT = nil
}

// some network modes require a DHCP/DNS server
// set the addresses for DHCP
// set the addresses
if addresses, ok := d.GetOk("addresses"); ok {
ipsPtrsLst := []libvirtxml.NetworkIP{}
for _, addressI := range addresses.([]interface{}) {
address := addressI.(string)
// get the IP address entry for this subnet (with a guessed DHCP range)
dni, dhcp, err := setNetworkIP(addressI.(string))
if err != nil {
return err
}
if d.Get("dhcp.0.enabled").(bool) {
dni.DHCP = dhcp
}

ipsPtrsLst = append(ipsPtrsLst, *dni)
}
networkDef.IPs = ipsPtrsLst
}

// set static routes
if routes, ok := d.GetOk("routes"); ok {
for _, routeI := range routes.([]interface{}) {
route := libvirtxml.NetworkRoute{}

routeComponents := strings.Split(routeI.(string), "->")
if len(routeComponents) != 2 {
return fmt.Errorf("Error parsing address '%s'", routeI.(string))
}

address := strings.TrimSpace(routeComponents[0])
gateway := strings.TrimSpace(routeComponents[1])

// parse the address
_, ipNet, err := net.ParseCIDR(address)
if err != nil {
return fmt.Errorf("Error parsing addresses definition '%s': %s", address, err)
return fmt.Errorf("Error parsing address '%s': %s", address, err)
}
ones, bits := ipNet.Mask.Size()
family := "ipv4"
if bits == (net.IPv6len * 8) {
family = "ipv6"
}
ipsRange := 2 ^ bits - 2 ^ ones
if ipsRange < 4 {
return fmt.Errorf("Netmask seems to be too strict: only %d IPs available (%s)", ipsRange-3, family)
route.Address = ipNet.IP.String()
route.Prefix = strconv.Itoa(ones)
route.Family = family

// parse and check the gateway
parsedGateway := net.ParseIP(gateway)
if parsedGateway == nil {
return fmt.Errorf("Could not parse IP address '%s'", parsedGateway)
}
route.Gateway = parsedGateway.String()

// we should calculate the range served by DHCP. For example, for
// 192.168.121.0/24 we will serve 192.168.121.2 - 192.168.121.254
start, end := NetworkRange(ipNet)
networkDef.Routes = append(networkDef.Routes, route)
}
}

// skip the .0, (for the network),
start[len(start)-1]++
if _, ok := d.GetOk("dns.0"); ok {
dnsPrefix := "dns.0"
dns := libvirtxml.NetworkDNS{}

// assign the .1 to the host interface
dni := libvirtxml.NetworkIP{
Address: start.String(),
Prefix: strconv.Itoa(ones),
Family: family,
}
if d.Get(dnsPrefix + ".enabled").(bool) {
dns.Enable = "yes"
}

start[len(start)-1]++ // then skip the .1
end[len(end)-1]-- // and skip the .255 (for broadcast)
if forwarders, ok := d.GetOk(dnsPrefix + ".forwarders"); ok {
dns.Forwarders = []libvirtxml.NetworkDNSForwarder{}

for _, forwardersI := range forwarders.([]interface{}) {
forwarderSpec := forwardersI.(string)
ip := ""
domain := ""

forwarderComponents := strings.Split(forwarderSpec, "->")
if len(forwarderComponents) == 1 {
// the first element can be an IP or a domain: we must identify the class
target := strings.TrimSpace(forwarderComponents[0])
parsedIP := net.ParseIP(target)
if parsedIP != nil {
ip = parsedIP.String()
} else {
domain = target
}
} else if len(forwarderComponents) == 2 {
domain = strings.TrimSpace(forwarderComponents[0])
ip = strings.TrimSpace(forwarderComponents[1])
} else {
return fmt.Errorf("Error parsing forwarder '%s'", forwarders.(string))
}

dni.DHCP = &libvirtxml.NetworkDHCP{
Ranges: []libvirtxml.NetworkDHCPRange{
{
Start: start.String(),
End: end.String(),
},
},
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return fmt.Errorf("Could not parse address in forwarder specification '%s'", forwarderSpec)
}

dns.Forwarders = append(dns.Forwarders, libvirtxml.NetworkDNSForwarder{Addr: parsedIP.String(), Domain: domain})
}
ipsPtrsLst = append(ipsPtrsLst, dni)
}
networkDef.IPs = ipsPtrsLst
}

if dnsForwardCount, ok := d.GetOk("dns_forwarder.#"); ok {
dns := libvirtxml.NetworkDNS{
Forwarders: []libvirtxml.NetworkDNSForwarder{},
}
if _, ok := d.GetOk(dnsPrefix + ".host"); ok {
dns.Host = &libvirtxml.NetworkDNSHost{}
hostPrefix := dnsPrefix + ".host"

for i := 0; i < dnsForwardCount.(int); i++ {
forward := libvirtxml.NetworkDNSForwarder{}
forwardPrefix := fmt.Sprintf("dns_forwarder.%d", i)
if address, ok := d.GetOk(forwardPrefix + ".address"); ok {
ip := net.ParseIP(address.(string))
if ip == nil {
return fmt.Errorf("Could not parse address '%s'", address)
if address, ok := d.GetOk(hostPrefix + ".address"); ok {
parsedIP := net.ParseIP(address.(string))
if parsedIP == nil {
return fmt.Errorf("Could not parse IP address '%s'", parsedIP)
}
forward.Addr = ip.String()
dns.Host.IP = parsedIP.String()
}
if domain, ok := d.GetOk(forwardPrefix + ".domain"); ok {
forward.Domain = domain.(string)

if dnsHostCount, ok := d.GetOk(hostPrefix + ".name.#"); ok {
dns.Host.Hostnames = []libvirtxml.NetworkDNSHostHostname{}

for i := 0; i < dnsHostCount.(int); i++ {
dnsHostNamePrefix := fmt.Sprintf(dnsPrefix+".name.%d", i)
if name, ok := d.GetOk(dnsHostNamePrefix); ok {
hostname := libvirtxml.NetworkDNSHostHostname{Hostname: name.(string)}
dns.Host.Hostnames = append(dns.Host.Hostnames, hostname)
}
}
}
dns.Forwarders = append(dns.Forwarders, forward)
}
networkDef.DNS = &dns
}

} else if networkDef.Forward.Mode == netModeBridge {
Expand Down Expand Up @@ -368,6 +484,7 @@ func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error reading network autostart setting: %s", err)
}
d.Set("autostart", autostart)

addresses := []string{}
for _, address := range networkDef.IPs {
// we get the host interface IP (ie, 10.10.8.1) but we want the network CIDR (ie, 10.10.8.0/24)
Expand All @@ -390,7 +507,10 @@ func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error
d.Set("addresses", addresses)
}

// TODO: get any other parameters from the network and save them
// TODO: get any other parameters from the network and save them (ie, DNS forwarders...)

d.Set("dns.0.local_only", networkDef.Domain != nil && strings.ToLower(networkDef.Domain.LocalOnly) == "yes")
d.Set("dns.0.enabled", networkDef.DNS != nil && strings.ToLower(networkDef.DNS.Enable) == "yes")

log.Printf("[DEBUG] Network ID %s successfully read", d.Id())
return nil
Expand Down Expand Up @@ -444,6 +564,50 @@ func resourceLibvirtNetworkDelete(d *schema.ResourceData, meta interface{}) erro
return nil
}

func setNetworkIP(address string) (*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)
}
ones, bits := ipNet.Mask.Size()
family := "ipv4"
if bits == (net.IPv6len * 8) {
family = "ipv6"
}
ipsRange := 2 ^ bits - 2 ^ ones
if ipsRange < 4 {
return nil, nil, fmt.Errorf("Netmask seems to be too strict: only %d IPs available (%s)", ipsRange-3, family)
}

// we should calculate the range served by DHCP. For example, for
// 192.168.121.0/24 we will serve 192.168.121.2 - 192.168.121.254
start, end := networkRange(ipNet)

// skip the .0, (for the network),
start[len(start)-1]++

// assign the .1 to the host interface
dni := &libvirtxml.NetworkIP{
Address: start.String(),
Prefix: strconv.Itoa(ones),
Family: family,
}

start[len(start)-1]++ // then skip the .1
end[len(end)-1]-- // and skip the .255 (for broadcast)

dhcp := &libvirtxml.NetworkDHCP{
Ranges: []libvirtxml.NetworkDHCPRange{
{
Start: start.String(),
End: end.String(),
},
},
}

return dni, dhcp, nil
}

func waitForNetworkActive(network libvirt.Network) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
active, err := network.IsActive()
Expand Down
Loading

0 comments on commit ce90234

Please sign in to comment.