Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libvirt/resource_libvirt_network: Support updates for dns.hosts #469

Merged
merged 1 commit into from
Nov 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions libvirt/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package libvirt

import (
"errors"
"fmt"
"reflect"
"sort"

"github.com/hashicorp/terraform/helper/schema"
libvirt "github.com/libvirt/libvirt-go"
"github.com/libvirt/libvirt-go-xml"
)

func resourceLibvirtNetworkUpdateDNSHosts(d *schema.ResourceData, network *libvirt.Network) error {
hostsKey := dnsPrefix + ".hosts"
if d.HasChange(hostsKey) {
oldInterface, newInterface := d.GetChange(hostsKey)

oldEntries, err := parseNetworkDNSHostsChange(oldInterface)
if err != nil {
return fmt.Errorf("parse old %s: %s", hostsKey, err)
}

newEntries, err := parseNetworkDNSHostsChange(newInterface)
if err != nil {
return fmt.Errorf("parse new %s: %s", hostsKey, err)
}

for _, oldEntry := range oldEntries {
found := false
for _, newEntry := range newEntries {
if reflect.DeepEqual(newEntry, oldEntry) {
found = true
break
}
}
if found {
continue
}

data, err := xmlMarshallIndented(libvirtxml.NetworkDNSHost{IP: oldEntry.IP})
if err != nil {
return fmt.Errorf("serialize update: %s", err)
}

err = network.Update(libvirt.NETWORK_UPDATE_COMMAND_DELETE, libvirt.NETWORK_SECTION_DNS_HOST, -1, data, libvirt.NETWORK_UPDATE_AFFECT_LIVE|libvirt.NETWORK_UPDATE_AFFECT_CONFIG)
if err != nil {
return fmt.Errorf("delete %s: %s", oldEntry.IP, err)
}
}

for _, newEntry := range newEntries {
found := false
for _, oldEntry := range oldEntries {
if reflect.DeepEqual(oldEntry, newEntry) {
found = true
break
}
}
if found {
continue
}

data, err := xmlMarshallIndented(newEntry)
if err != nil {
return fmt.Errorf("serialize update: %s", err)
}

err = network.Update(libvirt.NETWORK_UPDATE_COMMAND_ADD_LAST, libvirt.NETWORK_SECTION_DNS_HOST, -1, data, libvirt.NETWORK_UPDATE_AFFECT_LIVE|libvirt.NETWORK_UPDATE_AFFECT_CONFIG)
if err != nil {
return fmt.Errorf("add %v: %s", newEntry, err)
}
}

d.SetPartial(hostsKey)
}

return nil
}

func parseNetworkDNSHostsChange(change interface{}) (entries []libvirtxml.NetworkDNSHost, err error) {
slice, ok := change.([]interface{})
if !ok {
return entries, errors.New("not slice")
}

mapEntries := map[string][]string{}
for i, entryInterface := range slice {
entryMap, ok := entryInterface.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("entry %d is not a map", i)
}

ipInterface, ok := entryMap["ip"]
if !ok {
return nil, fmt.Errorf("entry %d.ip is missing", i)
}

ip, ok := ipInterface.(string)
if !ok {
return nil, fmt.Errorf("entry %d.ip is not a string", i)
}

hostnameInterface, ok := entryMap["hostname"]
if !ok {
return nil, fmt.Errorf("entry %d.hostname is missing", i)
}

hostname, ok := hostnameInterface.(string)
if !ok {
return nil, fmt.Errorf("entry %d.hostname is not a string", i)
}

_, ok = mapEntries[ip]
if ok {
mapEntries[ip] = append(mapEntries[ip], hostname)
} else {
mapEntries[ip] = []string{hostname}
}
}

entries = make([]libvirtxml.NetworkDNSHost, 0, len(mapEntries))
for ip, hostnames := range mapEntries {
sort.Strings(hostnames)
xmlHostnames := make([]libvirtxml.NetworkDNSHostHostname, 0, len(hostnames))
for _, hostname := range hostnames {
xmlHostnames = append(xmlHostnames, libvirtxml.NetworkDNSHostHostname{
Hostname: hostname,
})
}
entries = append(entries, libvirtxml.NetworkDNSHost{
IP: ip,
Hostnames: xmlHostnames,
})
}

return entries, nil
}
9 changes: 6 additions & 3 deletions libvirt/resource_libvirt_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ func resourceLibvirtNetwork() *schema.Resource {
"hosts": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip": {
Expand All @@ -179,15 +178,13 @@ func resourceLibvirtNetwork() *schema.Resource {
// and therefore doesn't recognize that this is set when assigning from
// a rendered dns_host template.
Optional: true,
ForceNew: true,
},
"hostname": {
Type: schema.TypeString,
// This should be required, but Terraform does validation too early
// and therefore doesn't recognize that this is set when assigning from
// a rendered dns_host template.
Optional: true,
ForceNew: true,
},
},
},
Expand Down Expand Up @@ -266,6 +263,12 @@ func resourceLibvirtNetworkUpdate(d *schema.ResourceData, meta interface{}) erro
}
d.SetPartial("autostart")
}

err = resourceLibvirtNetworkUpdateDNSHosts(d, network)
if err != nil {
return fmt.Errorf("update DNS hosts: %s", err)
}

d.Partial(false)
return nil
}
Expand Down
70 changes: 70 additions & 0 deletions libvirt/resource_libvirt_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,76 @@ func TestAccLibvirtNetwork_DNSHosts(t *testing.T) {
}),
),
},
{
Config: fmt.Sprintf(`
resource "libvirt_network" "%s" {
name = "%s"
domain = "k8s.local"
addresses = ["10.17.3.0/24"]
dns {
hosts = [
{
hostname = "myhost1",
ip = "1.1.1.1",
},
]
}
}`, randomNetworkResource, randomNetworkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dns.0.hosts.0.hostname", "myhost1"),
resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dns.0.hosts.0.ip", "1.1.1.1"),
checkDNSHosts("libvirt_network."+randomNetworkResource, []libvirtxml.NetworkDNSHost{
{
IP: "1.1.1.1",
Hostnames: []libvirtxml.NetworkDNSHostHostname{
{Hostname: "myhost1"},
},
},
}),
),
},
{
Config: fmt.Sprintf(`
resource "libvirt_network" "%s" {
name = "%s"
domain = "k8s.local"
addresses = ["10.17.3.0/24"]
dns {
hosts = [
{
hostname = "myhost1",
ip = "1.1.1.1",
},
# Without https:#www.redhat.com/archives/libvir-list/2018-November/msg00231.html, this raises:
#
# update DNS hosts: add {{ } 1.1.1.2 [{myhost1}]}: virError(Code=55, Domain=19, Message='Requested operation is not valid: there is already at least one DNS HOST record with a matching field in network fo64d9y6w9')
# {
# hostname = "myhost1",
# ip = "1.1.1.2",
# },
{
hostname = "myhost2",
ip = "1.1.1.1",
},
]
}
}`, randomNetworkResource, randomNetworkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dns.0.hosts.0.hostname", "myhost1"),
resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dns.0.hosts.0.ip", "1.1.1.1"),
resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dns.0.hosts.1.hostname", "myhost2"),
resource.TestCheckResourceAttr("libvirt_network."+randomNetworkResource, "dns.0.hosts.1.ip", "1.1.1.1"),
checkDNSHosts("libvirt_network."+randomNetworkResource, []libvirtxml.NetworkDNSHost{
{
IP: "1.1.1.1",
Hostnames: []libvirtxml.NetworkDNSHostHostname{
{Hostname: "myhost1"},
{Hostname: "myhost2"},
},
},
}),
),
},
},
})
}
Expand Down