diff --git a/pkg/terraform/exec/plugins/Gopkg.lock b/pkg/terraform/exec/plugins/Gopkg.lock index 9e0fa2b68da..8b7403adf8c 100644 --- a/pkg/terraform/exec/plugins/Gopkg.lock +++ b/pkg/terraform/exec/plugins/Gopkg.lock @@ -352,11 +352,14 @@ version = "v1.1.0" [[projects]] - digest = "1:e101085bd88d5999c0cc346c222d4afb3aebc169c23f3126fa8987fb92badaec" + digest = "1:ed022e6cec96994a6cc5fcdd73f1c85674a802ba872f6608a3849f8e9cc144bf" name = "github.com/dmacvicar/terraform-provider-libvirt" - packages = ["libvirt"] + packages = [ + "libvirt", + "libvirt/helper/suppress", + ] pruneopts = "NUT" - revision = "2ad0228349b2d3b487a2ada25d1a0eb40d73b7d1" + revision = "5417057da4ea76505889ce96e762bdc36dd6894e" [[projects]] branch = "master" diff --git a/pkg/terraform/exec/plugins/Gopkg.toml b/pkg/terraform/exec/plugins/Gopkg.toml index b69b3b4143a..5b7145432cf 100644 --- a/pkg/terraform/exec/plugins/Gopkg.toml +++ b/pkg/terraform/exec/plugins/Gopkg.toml @@ -11,7 +11,7 @@ ignored = [ [[constraint]] name = "github.com/dmacvicar/terraform-provider-libvirt" - revision = "2ad0228349b2d3b487a2ada25d1a0eb40d73b7d1" + revision = "5417057da4ea76505889ce96e762bdc36dd6894e" [[constraint]] name = "github.com/terraform-providers/terraform-provider-aws" diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/acceptance_tests_functions_helpers.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/acceptance_tests_functions_helpers.go deleted file mode 100644 index 2ffe739d1a1..00000000000 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/acceptance_tests_functions_helpers.go +++ /dev/null @@ -1,64 +0,0 @@ -package libvirt - -import ( - "fmt" - "log" - - "github.com/hashicorp/terraform/terraform" - libvirt "github.com/libvirt/libvirt-go" - "github.com/libvirt/libvirt-go-xml" -) - -// This file contain function helpers used for testsuite/testacc - -// the following helpers are used in mostly all testacc. - -// getResourceFromTerraformState get aresource by name -// from terraform states produced during testacc -// and return the resource -func getResourceFromTerraformState(resourceName string, state *terraform.State) (*terraform.ResourceState, error) { - rs, ok := state.RootModule().Resources[resourceName] - if !ok { - return nil, fmt.Errorf("Not found: %s", resourceName) - } - - if rs.Primary.ID == "" { - return nil, fmt.Errorf("No libvirt resource key ID is set") - } - return rs, nil -} - -// ** resource specifics helpers ** - -// getVolumeFromTerraformState lookup volume by name and return the libvirt volume from a terraform state -func getVolumeFromTerraformState(name string, state *terraform.State, virConn libvirt.Connect) (*libvirt.StorageVol, error) { - rs, err := getResourceFromTerraformState(name, state) - if err != nil { - return nil, err - } - - vol, err := virConn.LookupStorageVolByKey(rs.Primary.ID) - if err != nil { - return nil, err - } - log.Printf("[DEBUG]:The ID is %s", rs.Primary.ID) - return vol, nil -} - -// helper used in network tests for retrieve xml network definition. -func getNetworkDef(state *terraform.State, name string, virConn libvirt.Connect) (*libvirtxml.Network, error) { - var network *libvirt.Network - rs, err := getResourceFromTerraformState(name, state) - if err != nil { - return nil, err - } - network, err = virConn.LookupNetworkByUUIDString(rs.Primary.ID) - if err != nil { - return nil, err - } - networkDef, err := getXMLNetworkDefFromLibvirt(network) - if err != nil { - return nil, fmt.Errorf("Error reading libvirt network XML description: %s", err) - } - return &networkDef, nil -} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/cloudinit_def.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/cloudinit_def.go index 3d25631a4a7..f4505e91eba 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/cloudinit_def.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/cloudinit_def.go @@ -307,6 +307,8 @@ func readIso9660File(file os.FileInfo) ([]byte, error) { // pointer when you are done. func downloadISO(virConn *libvirt.Connect, volume libvirt.StorageVol) (*os.File, error) { // get Volume info (required to get size later) + var bytesCopied int64 + info, err := volume.GetInfo() if err != nil { return nil, fmt.Errorf("Error retrieving info for volume: %s", err) @@ -323,18 +325,36 @@ func downloadISO(virConn *libvirt.Connect, volume libvirt.StorageVol) (*os.File, if err != nil { return tmpFile, err } - defer stream.Finish() - volume.Download(stream, 0, info.Capacity, 0) + defer func() { + stream.Free() + }() + err = volume.Download(stream, 0, info.Capacity, 0) + if err != nil { + stream.Abort() + return tmpFile, fmt.Errorf("Error by downloading content to libvirt volume:%s", err) + } sio := NewStreamIO(*stream) - n, err := io.Copy(tmpFile, sio) + bytesCopied, err = io.Copy(tmpFile, sio) if err != nil { return tmpFile, fmt.Errorf("Error while copying remote volume to local disk: %s", err) } + + if uint64(bytesCopied) != info.Capacity { + stream.Abort() + return tmpFile, fmt.Errorf("Error while copying remote volume to local disk, bytesCopied %d != %d volume.size", bytesCopied, info.Capacity) + } + + err = stream.Finish() + if err != nil { + stream.Abort() + return tmpFile, fmt.Errorf("Error by terminating libvirt stream %s", err) + } + tmpFile.Seek(0, 0) - log.Printf("%d bytes downloaded", n) + log.Printf("%d bytes downloaded", bytesCopied) return tmpFile, nil } diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go index 04efe7b096d..cc63339e7c8 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain.go @@ -202,7 +202,8 @@ func newDiskForCloudInit(virConn *libvirt.Connect, volumeKey string) (libvirtxml disk := libvirtxml.DomainDisk{ Device: "cdrom", Target: &libvirtxml.DomainDiskTarget{ - Dev: "hda", + // Last device letter possible with a single IDE controller on i440FX + Dev: "hdd", Bus: "ide", }, Driver: &libvirtxml.DomainDiskDriver{ @@ -251,6 +252,19 @@ func setCoreOSIgnition(d *schema.ResourceData, domainDef *libvirtxml.Domain) err return nil } +func setVideo(d *schema.ResourceData, domainDef *libvirtxml.Domain) error { + prefix := "video.0" + if _, ok := d.GetOk(prefix); ok { + domainDef.Devices.Videos = append(domainDef.Devices.Videos, libvirtxml.DomainVideo{ + Model: libvirtxml.DomainVideoModel{ + Type: d.Get(prefix + ".type").(string), + }, + }) + } + + return nil +} + func setGraphics(d *schema.ResourceData, domainDef *libvirtxml.Domain, arch string) error { if arch == "s390x" || arch == "ppc64" { domainDef.Devices.Graphics = nil @@ -271,7 +285,10 @@ func setGraphics(d *schema.ResourceData, domainDef *libvirtxml.Domain, arch stri if listenType, ok := d.GetOk(prefix + ".listen_type"); ok { switch listenType { case "address": - listener.Address = &libvirtxml.DomainGraphicListenerAddress{} + listenAddress := d.Get(prefix + ".listen_address") + listener.Address = &libvirtxml.DomainGraphicListenerAddress{ + Address: listenAddress.(string), + } case "network": listener.Network = &libvirtxml.DomainGraphicListenerNetwork{} case "socket": @@ -392,6 +409,8 @@ func setConsoles(d *schema.ResourceData, domainDef *libvirtxml.Domain) { func setDisks(d *schema.ResourceData, domainDef *libvirtxml.Domain, virConn *libvirt.Connect) error { var scsiDisk = false + var numOfISOs = 0 + for i := 0; i < d.Get("disk.#").(int); i++ { disk := newDefDisk(i) @@ -496,13 +515,15 @@ func setDisks(d *schema.ResourceData, domainDef *libvirtxml.Domain, virConn *lib if strings.HasSuffix(file.(string), ".iso") { disk.Device = "cdrom" disk.Target = &libvirtxml.DomainDiskTarget{ - Dev: "hda", + Dev: fmt.Sprintf("hd%s", diskLetterForIndex(numOfISOs)), Bus: "ide", } disk.Driver = &libvirtxml.DomainDiskDriver{ Name: "qemu", Type: "raw", } + + numOfISOs++ } } @@ -660,17 +681,16 @@ func setNetworkInterfaces(d *schema.ResourceData, domainDef *libvirtxml.Domain, break } } - if !wait { - return fmt.Errorf("Cannot map '%s': we are not waiting for DHCP lease and no IP has been provided", hostname) - } - // the resource specifies a hostname but not an IP, so we must wait until we - // have a valid lease and then read the IP we have been assigned, so we can - // do the mapping - log.Printf("[DEBUG] Do not have an IP for '%s' yet: will wait until DHCP provides one...", hostname) - partialNetIfaces[strings.ToUpper(mac)] = &pendingMapping{ - mac: strings.ToUpper(mac), - hostname: hostname, - network: network, + if wait { + // the resource specifies a hostname but not an IP, so we must wait until we + // have a valid lease and then read the IP we have been assigned, so we can + // do the mapping + log.Printf("[DEBUG] Do not have an IP for '%s' yet: will wait until DHCP provides one...", hostname) + partialNetIfaces[strings.ToUpper(mac)] = &pendingMapping{ + mac: strings.ToUpper(mac), + hostname: hostname, + network: network, + } } } } diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain_def.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain_def.go index aefc222bc70..476e12cd784 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain_def.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/domain_def.go @@ -73,7 +73,7 @@ func newDomainDef() libvirtxml.Domain { { Model: "virtio", Backend: &libvirtxml.DomainRNGBackend{ - Random: &libvirtxml.DomainRNGBackendRandom{}, + Random: &libvirtxml.DomainRNGBackendRandom{Device: "/dev/urandom"}, }, }, }, diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/helper/suppress/strings.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/helper/suppress/strings.go new file mode 100644 index 00000000000..b64bdc73f2e --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/helper/suppress/strings.go @@ -0,0 +1,10 @@ +package suppress + +import ( + "strings" + "github.com/hashicorp/terraform/helper/schema" +) + +func CaseDifference(_, old, new string, _ *schema.ResourceData) bool { + return strings.EqualFold(old, new) +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network.go index f990c2f9721..707113d0abe 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network.go @@ -1,145 +1,17 @@ package libvirt import ( - "errors" "fmt" "log" "net" - "reflect" - "sort" + "strings" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - libvirt "github.com/libvirt/libvirt-go" + "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 -} - func waitForNetworkActive(network libvirt.Network) resource.StateRefreshFunc { return func() (interface{}, string, error) { active, err := network.IsActive() @@ -153,7 +25,7 @@ func waitForNetworkActive(network libvirt.Network) resource.StateRefreshFunc { } } -// wait for network to be up and timeout after 5 minutes. +// waitForNetworkDestroyed waits for a network to destroyed func waitForNetworkDestroyed(virConn *libvirt.Connect, uuid string) resource.StateRefreshFunc { return func() (interface{}, string, error) { log.Printf("Waiting for network %s to be destroyed", uuid) @@ -166,31 +38,54 @@ func waitForNetworkDestroyed(virConn *libvirt.Connect, uuid string) resource.Sta } } -func setDhcpByCIDRAdressesSubnets(d *schema.ResourceData, networkDef *libvirtxml.Network) error { - if addresses, ok := d.GetOk("addresses"); ok { - ipsPtrsLst := []libvirtxml.NetworkIP{} - for _, addressI := range addresses.([]interface{}) { - // 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 - } else { - // if a network exist with enabled but an user want to disable - // dhcp, we need to set dhcp struct to nil. - dni.DHCP = nil - } - - ipsPtrsLst = append(ipsPtrsLst, *dni) +// getNetModeFromResource returns the network mode fromm a network definition +func getNetModeFromResource(d *schema.ResourceData) string { + return strings.ToLower(d.Get("mode").(string)) +} + +// getIPsFromResource gets the IPs configurations from the resource definition +func getIPsFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkIP, error) { + addresses, ok := d.GetOk("addresses") + if !ok { + return []libvirtxml.NetworkIP{}, nil + } + + // check if DHCP must be enabled by default + var dhcpEnabled bool + netMode := getNetModeFromResource(d) + if netMode == netModeIsolated || netMode == netModeNat || netMode == netModeRoute { + dhcpEnabled = true + } + + ipsPtrsLst := []libvirtxml.NetworkIP{} + for num, addressI := range addresses.([]interface{}) { + // get the IP address entry for this subnet (with a guessed DHCP range) + dni, dhcp, err := getNetworkIPConfig(addressI.(string)) + if err != nil { + return nil, err } - networkDef.IPs = ipsPtrsLst + + dhcpKey := fmt.Sprintf("dhcp.%d.enabled", num) + dhcpEnabledByUser, ok := d.GetOkExists(dhcpKey) + if ok { + dhcpEnabled = dhcpEnabledByUser.(bool) + } + + if dhcpEnabled { + dni.DHCP = dhcp + } else { + // if a network exist with enabled but an user want to disable it + // we need to set DHCP struct to nil. + dni.DHCP = nil + } + + ipsPtrsLst = append(ipsPtrsLst, *dni) } - return nil + + return ipsPtrsLst, nil } -func setNetworkIP(address string) (*libvirtxml.NetworkIP, *libvirtxml.NetworkDHCP, error) { +func getNetworkIPConfig(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) @@ -233,3 +128,49 @@ func setNetworkIP(address string) (*libvirtxml.NetworkIP, *libvirtxml.NetworkDHC return dni, dhcp, nil } + +// getBridgeFromResource returns a libvirt's NetworkBridge +// from the ResourceData provided. +func getBridgeFromResource(d *schema.ResourceData) *libvirtxml.NetworkBridge { + // use a bridge provided by the user, or create one otherwise (libvirt will assign on automatically when empty) + bridgeName := "" + if b, ok := d.GetOk("bridge"); ok { + bridgeName = b.(string) + } + + bridge := &libvirtxml.NetworkBridge{ + Name: bridgeName, + STP: "on", + } + + return bridge +} + +// getDomainFromResource returns a libvirt's NetworkDomain +// from the ResourceData provided. +func getDomainFromResource(d *schema.ResourceData) *libvirtxml.NetworkDomain { + domainName, ok := d.GetOk("domain") + if !ok { + return nil + } + + domain := &libvirtxml.NetworkDomain{ + Name: domainName.(string), + } + + if dnsLocalOnly, ok := d.GetOk(dnsPrefix + ".local_only"); ok { + if dnsLocalOnly.(bool) { + domain.LocalOnly = "yes" // this "boolean" must be "yes"|"no" + } + } + + return domain +} + +func getMTUFromResource(d *schema.ResourceData) *libvirtxml.NetworkMTU { + if mtu, ok := d.GetOk("mtu"); ok { + return &libvirtxml.NetworkMTU{Size: uint(mtu.(int))} + } + + return nil +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network_dns.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network_dns.go new file mode 100644 index 00000000000..68cc9938de9 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network_dns.go @@ -0,0 +1,262 @@ +package libvirt + +import ( + "errors" + "fmt" + "net" + "reflect" + "sort" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/libvirt/libvirt-go" + "github.com/libvirt/libvirt-go-xml" +) + +// updateDNSHosts detects changes in the DNS hosts entries +// updating the network definition accordingly +func updateDNSHosts(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) + } + + // process all the old DNS entries that must be removed + 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) + } + } + + // process all the new DNS entries that must be added + 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 +} + +// getDNSHostsFromResource returns a list of libvirt's DNS hosts +// from the network definition +func getDNSHostsFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkDNSHost, error) { + dnsHostsMap := map[string][]string{} + if dnsHostCount, ok := d.GetOk(dnsPrefix + ".hosts.#"); ok { + for i := 0; i < dnsHostCount.(int); i++ { + hostPrefix := fmt.Sprintf(dnsPrefix+".hosts.%d", i) + + address := d.Get(hostPrefix + ".ip").(string) + if net.ParseIP(address) == nil { + return nil, fmt.Errorf("Could not parse address '%s'", address) + } + + dnsHostsMap[address] = append(dnsHostsMap[address], d.Get(hostPrefix+".hostname").(string)) + } + } + + var dnsHosts []libvirtxml.NetworkDNSHost + + for ip, hostnames := range dnsHostsMap { + dnsHostnames := []libvirtxml.NetworkDNSHostHostname{} + for _, hostname := range hostnames { + dnsHostnames = append(dnsHostnames, libvirtxml.NetworkDNSHostHostname{Hostname: hostname}) + } + dnsHosts = append(dnsHosts, libvirtxml.NetworkDNSHost{ + IP: ip, + Hostnames: dnsHostnames, + }) + } + + return dnsHosts, nil +} + +// getDNSForwardersFromResource returns the list of libvirt's DNS forwarders +// in the network definition +func getDNSForwardersFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkDNSForwarder, error) { + var dnsForwarders []libvirtxml.NetworkDNSForwarder + if dnsForwardCount, ok := d.GetOk(dnsPrefix + ".forwarders.#"); ok { + for i := 0; i < dnsForwardCount.(int); i++ { + forward := libvirtxml.NetworkDNSForwarder{} + forwardPrefix := fmt.Sprintf(dnsPrefix+".forwarders.%d", i) + if address, ok := d.GetOk(forwardPrefix + ".address"); ok { + ip := net.ParseIP(address.(string)) + if ip == nil { + return nil, fmt.Errorf("Could not parse address '%s'", address) + } + forward.Addr = ip.String() + } + if domain, ok := d.GetOk(forwardPrefix + ".domain"); ok { + forward.Domain = domain.(string) + } + dnsForwarders = append(dnsForwarders, forward) + } + } + + return dnsForwarders, nil +} + +// getDNSEnableFromResource returns string to enable ("yes") or disable ("no") dns +// in the network definition +func getDNSEnableFromResource(d *schema.ResourceData) (string, error) { + if dnsLocalOnly, ok := d.GetOk(dnsPrefix + ".enabled"); ok { + if dnsLocalOnly.(bool) { + return "yes", nil // this "boolean" must be "yes"|"no" + } + } + return "no", nil +} + +// getDNSSRVFromResource returns a list of libvirt's DNS SRVs +// in the network definition +func getDNSSRVFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkDNSSRV, error) { + var dnsSRVs []libvirtxml.NetworkDNSSRV + + if dnsSRVCount, ok := d.GetOk(dnsPrefix + ".srvs.#"); ok { + for i := 0; i < dnsSRVCount.(int); i++ { + srv := libvirtxml.NetworkDNSSRV{} + srvPrefix := fmt.Sprintf(dnsPrefix+".srvs.%d", i) + if service, ok := d.GetOk(srvPrefix + ".service"); ok { + srv.Service = service.(string) + } + if protocol, ok := d.GetOk(srvPrefix + ".protocol"); ok { + srv.Protocol = protocol.(string) + } + if domain, ok := d.GetOk(srvPrefix + ".domain"); ok { + srv.Domain = domain.(string) + } + if target, ok := d.GetOk(srvPrefix + ".target"); ok { + srv.Target = target.(string) + } + if port, ok := d.GetOk(srvPrefix + ".port"); ok { + p, err := strconv.Atoi(port.(string)) + if err != nil { + return nil, fmt.Errorf("Could not convert port '%s' to int", port) + } + srv.Port = uint(p) + } + if weight, ok := d.GetOk(srvPrefix + ".weight"); ok { + w, err := strconv.Atoi(weight.(string)) + if err != nil { + return nil, fmt.Errorf("Could not convert weight '%s' to int", weight) + } + srv.Weight = uint(w) + } + if priority, ok := d.GetOk(srvPrefix + ".priority"); ok { + w, err := strconv.Atoi(priority.(string)) + if err != nil { + return nil, fmt.Errorf("Could not convert priority '%s' to int", priority) + } + srv.Priority = uint(w) + } + dnsSRVs = append(dnsSRVs, srv) + } + } + + return dnsSRVs, nil +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network_routes.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network_routes.go new file mode 100644 index 00000000000..b09e3f48d65 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/network_routes.go @@ -0,0 +1,58 @@ +package libvirt + +import ( + "fmt" + "log" + "net" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/libvirt/libvirt-go-xml" +) + +// getRoutesFromResource gets the libvirt network routes from a network definition +func getRoutesFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkRoute, error) { + routesCount, ok := d.GetOk("routes.#") + if !ok { + log.Printf("[INFO] No routes defined") + return []libvirtxml.NetworkRoute{}, nil + } + + routes := []libvirtxml.NetworkRoute{} + log.Printf("[INFO] %d routes defined", routesCount) + for i := 0; i < routesCount.(int); i++ { + route := libvirtxml.NetworkRoute{} + routePrefix := fmt.Sprintf("routes.%d", i) + + if cidr, ok := d.GetOk(routePrefix + ".cidr"); ok { + addr, net, err := net.ParseCIDR(cidr.(string)) + if err != nil { + return nil, fmt.Errorf("Error parsing static route in network: %s", err) + } + + if addr.To4() == nil { + route.Family = "ipv6" + } + + route.Address = addr.String() + + ones, _ := net.Mask.Size() + route.Prefix = (uint)(ones) + } else { + return nil, fmt.Errorf("no address defined for static route") + } + + if gw, ok := d.GetOk(routePrefix + ".gateway"); ok { + ip := net.ParseIP(gw.(string)) + if ip == nil { + return nil, fmt.Errorf("Error parsing IP '%s' in static route in network", gw) + } + route.Gateway = ip.String() + } else { + return nil, fmt.Errorf("no gateway defined for static route") + } + + routes = append(routes, route) + } + + return routes, nil +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_cloud_init.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_cloud_init.go index d6af8fc6673..b119bce4563 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_cloud_init.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_cloud_init.go @@ -109,7 +109,7 @@ func resourceCloudInitDiskDelete(d *schema.ResourceData, meta interface{}) error return err } - return removeVolume(client, key) + return volumeDelete(client, key) } func resourceCloudInitDiskExists(d *schema.ResourceData, meta interface{}) (bool, error) { @@ -125,7 +125,7 @@ func resourceCloudInitDiskExists(d *schema.ResourceData, meta interface{}) (bool } volPoolName := d.Get("pool").(string) - volume, err := lookupVolumeReallyHard(client, volPoolName, key) + volume, err := volumeLookupReallyHard(client, volPoolName, key) if err != nil { return false, err } diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_coreos_ignition.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_coreos_ignition.go index 023ceef9c1d..3aa1d9e9b3f 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_coreos_ignition.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_coreos_ignition.go @@ -92,5 +92,5 @@ func resourceIgnitionDelete(d *schema.ResourceData, meta interface{}) error { return err } - return removeVolume(client, key) + return volumeDelete(client, key) } diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go index 317cc71dc44..7bf63e3ff9c 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_domain.go @@ -10,6 +10,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/dmacvicar/terraform-provider-libvirt/libvirt/helper/suppress" "github.com/hashicorp/terraform/helper/schema" libvirt "github.com/libvirt/libvirt-go" "github.com/libvirt/libvirt-go-xml" @@ -198,9 +199,10 @@ func resourceLibvirtDomain() *schema.Resource { Computed: true, }, "mac": { - Type: schema.TypeString, - Optional: true, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + DiffSuppressFunc: suppress.CaseDifference, }, "wait_for_lease": { Type: schema.TypeBool, @@ -238,6 +240,25 @@ func resourceLibvirtDomain() *schema.Resource { Optional: true, Default: "none", }, + "listen_address": { + Type: schema.TypeString, + Optional: true, + Default: "127.0.0.1", + }, + }, + }, + }, + "video": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Optional: true, + Default: "cirrus", + }, }, }, }, @@ -430,6 +451,7 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error return err } + setVideo(d, &domainDef) setConsoles(d, &domainDef) setCmdlineArgs(d, &domainDef) setFirmware(d, &domainDef) @@ -511,7 +533,15 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error if len(waitForLeases) > 0 { err = domainWaitForLeases(domain, waitForLeases, d.Timeout(schema.TimeoutCreate), d) if err != nil { - return err + ipNotFoundMsg := "Error: couldn't retrieve IP address of domain." + + "Please check following: \n" + + "1) is the domain running proplerly? \n" + + "2) has the network interface an IP address? \n" + + "3) Networking issues on your libvirt setup? \n " + + "4) is DHCP enabled on this Domain's network? \n" + + "5) if you use bridge network, the domain should have the pkg qemu-agent installed \n" + + "IMPORTANT: This error is not a terraform libvirt-provider error, but an error caused by your KVM/libvirt infrastructure configuration/setup" + return fmt.Errorf("%s \n %s", ipNotFoundMsg, err) } } diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_network.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_network.go index efb5c99ad05..819c15999b5 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_network.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_network.go @@ -4,13 +4,12 @@ import ( "fmt" "log" "net" - "strconv" "strings" "time" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - libvirt "github.com/libvirt/libvirt-go" + "github.com/libvirt/libvirt-go" "github.com/libvirt/libvirt-go-xml" ) @@ -55,7 +54,7 @@ func resourceLibvirtNetwork() *schema.Resource { "domain": { Type: schema.TypeString, Optional: true, - ForceNew: true, + ForceNew: false, }, "mode": { // can be "none", "nat" (default), "route", "bridge" Type: schema.TypeString, @@ -67,7 +66,12 @@ func resourceLibvirtNetwork() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ForceNew: true, + ForceNew: false, + }, + "mtu": { + Type: schema.TypeInt, + Optional: true, + Required: false, }, "addresses": { Type: schema.TypeList, @@ -85,10 +89,15 @@ func resourceLibvirtNetwork() *schema.Resource { "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, @@ -169,6 +178,7 @@ func resourceLibvirtNetwork() *schema.Resource { }, "hosts": { Type: schema.TypeList, + ForceNew: false, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -208,6 +218,23 @@ func resourceLibvirtNetwork() *schema.Resource { }, }, }, + "routes": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cidr": { + Type: schema.TypeString, + Required: true, + }, + "gateway": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, "xml": { Type: schema.TypeList, Optional: true, @@ -246,7 +273,10 @@ func resourceLibvirtNetworkExists(d *schema.ResourceData, meta interface{}) (boo return err == nil, err } +// resourceLibvirtNetworkUpdate updates dynamically some attributes in the network func resourceLibvirtNetworkUpdate(d *schema.ResourceData, meta interface{}) error { + // check the list of things that can be changed dynamically + // in https://wiki.libvirt.org/page/Networking#virsh_net-update virConn := meta.(*Client).libvirt if virConn == nil { return fmt.Errorf(LibVirtConIsNil) @@ -259,35 +289,80 @@ func resourceLibvirtNetworkUpdate(d *schema.ResourceData, meta interface{}) erro d.Partial(true) + networkName, err := network.GetName() + if err != nil { + return err + } + active, err := network.IsActive() if err != nil { - return fmt.Errorf("Error by getting network's status during update: %s", err) + return fmt.Errorf("Error when getting network %s status during update: %s", networkName, err) } if !active { - log.Printf("[DEBUG] Activating network") + log.Printf("[DEBUG] Activating network %s", networkName) if err := network.Create(); err != nil { - return fmt.Errorf("Error by activating network during update: %s", err) + return fmt.Errorf("Error when activating network %s during update: %s", networkName, err) } } if d.HasChange("autostart") { err = network.SetAutostart(d.Get("autostart").(bool)) if err != nil { - return fmt.Errorf("Error updating autostart for network: %s", err) + return fmt.Errorf("Error updating autostart for network %s: %s", networkName, err) } d.SetPartial("autostart") } - err = resourceLibvirtNetworkUpdateDNSHosts(d, network) + // detect changes in the DNS entries in this network + err = updateDNSHosts(d, network) if err != nil { - return fmt.Errorf("update DNS hosts: %s", err) + return fmt.Errorf("Error updating DNS hosts for network %s: %s", networkName, err) + } + + // detect changes in the bridge + if d.HasChange("bridge") { + networkBridge := getBridgeFromResource(d) + + data, err := xmlMarshallIndented(networkBridge) + if err != nil { + return fmt.Errorf("Error serializing update for network %s: %s", networkName, err) + } + + log.Printf("[DEBUG] Updating bridge for libvirt network '%s' with XML: %s", networkName, networkBridge.Name) + err = network.Update(libvirt.NETWORK_UPDATE_COMMAND_MODIFY, libvirt.NETWORK_SECTION_BRIDGE, -1, + data, libvirt.NETWORK_UPDATE_AFFECT_LIVE|libvirt.NETWORK_UPDATE_AFFECT_CONFIG) + if err != nil { + return fmt.Errorf("Error when updating bridge in %s: %s", networkName, err) + } + + d.SetPartial("bridge") + } + + // detect changes in the domain + if d.HasChange("domain") { + networkDomain := getDomainFromResource(d) + + data, err := xmlMarshallIndented(networkDomain) + if err != nil { + return fmt.Errorf("serialize update: %s", err) + } + + log.Printf("[DEBUG] Updating domain for libvirt network '%s' with XML: %s", networkName, data) + err = network.Update(libvirt.NETWORK_UPDATE_COMMAND_MODIFY, libvirt.NETWORK_SECTION_DOMAIN, -1, + data, libvirt.NETWORK_UPDATE_AFFECT_LIVE|libvirt.NETWORK_UPDATE_AFFECT_CONFIG) + if err != nil { + return fmt.Errorf("Error when updating domain in %s: %s", networkName, err) + } + + d.SetPartial("domain") } d.Partial(false) return nil } +// resourceLibvirtNetworkCreate creates a libvirt network from the resource definition func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) error { // see https://libvirt.org/formatnetwork.html virConn := meta.(*Client).libvirt @@ -297,32 +372,16 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro networkDef := newNetworkDef() networkDef.Name = d.Get("name").(string) - - if domain, ok := d.GetOk("domain"); ok { - networkDef.Domain = &libvirtxml.NetworkDomain{ - Name: domain.(string), - } - - if dnsLocalOnly, ok := d.GetOk(dnsPrefix + ".local_only"); ok { - if dnsLocalOnly.(bool) { - networkDef.Domain.LocalOnly = "yes" // this "boolean" must be "yes"|"no" - } - } - } + networkDef.Domain = getDomainFromResource(d) // use a bridge provided by the user, or create one otherwise (libvirt will assign on automatically when empty) - bridgeName := "" - if b, ok := d.GetOk("bridge"); ok { - bridgeName = b.(string) - } - networkDef.Bridge = &libvirtxml.NetworkBridge{ - Name: bridgeName, - STP: "on", - } + networkDef.Bridge = getBridgeFromResource(d) + + networkDef.MTU = getMTUFromResource(d) // check the network mode networkDef.Forward = &libvirtxml.NetworkForward{ - Mode: strings.ToLower(d.Get("mode").(string)), + Mode: getNetModeFromResource(d), } if networkDef.Forward.Mode == netModeIsolated || networkDef.Forward.Mode == netModeNat || networkDef.Forward.Mode == netModeRoute { @@ -333,109 +392,44 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro // there is no NAT when using a routed network networkDef.Forward.NAT = nil } + // if addresses are given set dhcp for these - err := setDhcpByCIDRAdressesSubnets(d, &networkDef) + ips, err := getIPsFromResource(d) if err != nil { return fmt.Errorf("Could not set DHCP from adresses '%s'", err) } - var dnsForwarders []libvirtxml.NetworkDNSForwarder - if dnsForwardCount, ok := d.GetOk(dnsPrefix + ".forwarders.#"); ok { - for i := 0; i < dnsForwardCount.(int); i++ { - forward := libvirtxml.NetworkDNSForwarder{} - forwardPrefix := fmt.Sprintf(dnsPrefix+".forwarders.%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) - } - forward.Addr = ip.String() - } - if domain, ok := d.GetOk(forwardPrefix + ".domain"); ok { - forward.Domain = domain.(string) - } - dnsForwarders = append(dnsForwarders, forward) - } - } + networkDef.IPs = ips - var dnsSRVs []libvirtxml.NetworkDNSSRV - if dnsSRVCount, ok := d.GetOk(dnsPrefix + ".srvs.#"); ok { - for i := 0; i < dnsSRVCount.(int); i++ { - srv := libvirtxml.NetworkDNSSRV{} - srvPrefix := fmt.Sprintf(dnsPrefix+".srvs.%d", i) - if service, ok := d.GetOk(srvPrefix + ".service"); ok { - srv.Service = service.(string) - } - if protocol, ok := d.GetOk(srvPrefix + ".protocol"); ok { - srv.Protocol = protocol.(string) - } - if domain, ok := d.GetOk(srvPrefix + ".domain"); ok { - srv.Domain = domain.(string) - } - if target, ok := d.GetOk(srvPrefix + ".target"); ok { - srv.Target = target.(string) - } - if port, ok := d.GetOk(srvPrefix + ".port"); ok { - p, err := strconv.Atoi(port.(string)) - if err != nil { - return fmt.Errorf("Could not convert port '%s' to int", port) - } - srv.Port = uint(p) - } - if weight, ok := d.GetOk(srvPrefix + ".weight"); ok { - w, err := strconv.Atoi(weight.(string)) - if err != nil { - return fmt.Errorf("Could not convert weight '%s' to int", weight) - } - srv.Weight = uint(w) - } - if priority, ok := d.GetOk(srvPrefix + ".priority"); ok { - w, err := strconv.Atoi(priority.(string)) - if err != nil { - return fmt.Errorf("Could not convert priority '%s' to int", priority) - } - srv.Priority = uint(w) - } - dnsSRVs = append(dnsSRVs, srv) - } + dnsEnabled, err := getDNSEnableFromResource(d) + if err != nil { + return err } - dnsHostsMap := map[string][]string{} - if dnsHostCount, ok := d.GetOk(dnsPrefix + ".hosts.#"); ok { - for i := 0; i < dnsHostCount.(int); i++ { - hostPrefix := fmt.Sprintf(dnsPrefix+".hosts.%d", i) - - address := d.Get(hostPrefix + ".ip").(string) - if net.ParseIP(address) == nil { - return fmt.Errorf("Could not parse address '%s'", address) - } + dnsForwarders, err := getDNSForwardersFromResource(d) + if err != nil { + return err + } - dnsHostsMap[address] = append(dnsHostsMap[address], d.Get(hostPrefix+".hostname").(string)) - } + dnsSRVs, err := getDNSSRVFromResource(d) + if err != nil { + return err } - var dnsHosts []libvirtxml.NetworkDNSHost - for ip, hostnames := range dnsHostsMap { - dnsHostnames := []libvirtxml.NetworkDNSHostHostname{} - for _, hostname := range hostnames { - dnsHostnames = append(dnsHostnames, libvirtxml.NetworkDNSHostHostname{Hostname: hostname}) - } - dnsHosts = append(dnsHosts, libvirtxml.NetworkDNSHost{ - IP: ip, - Hostnames: dnsHostnames, - }) + dnsHosts, err := getDNSHostsFromResource(d) + if err != nil { + return err } - if len(dnsForwarders) > 0 || len(dnsSRVs) > 0 || len(dnsHosts) > 0 { - dns := libvirtxml.NetworkDNS{ - Forwarders: dnsForwarders, - Host: dnsHosts, - SRVs: dnsSRVs, - } - networkDef.DNS = &dns + dns := libvirtxml.NetworkDNS{ + Enable: dnsEnabled, + Forwarders: dnsForwarders, + Host: dnsHosts, + SRVs: dnsSRVs, } + networkDef.DNS = &dns } else if networkDef.Forward.Mode == netModeBridge { - if bridgeName == "" { + if networkDef.Bridge.Name == "" { return fmt.Errorf("'bridge' must be provided when using the bridged network mode") } // Bridges cannot forward @@ -444,6 +438,13 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("unsupported network mode '%s'", networkDef.Forward.Mode) } + // parse any static routes + routes, err := getRoutesFromResource(d) + if err != nil { + return err + } + networkDef.Routes = routes + // once we have the network defined, connect to libvirt and create it from the XML serialization connectURI, err := virConn.GetURI() if err != nil { @@ -510,7 +511,11 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro return resourceLibvirtNetworkRead(d, meta) } +// resourceLibvirtNetworkRead gets the current resource from libvirt and creates +// the corresponding `schema.ResourceData` func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Read resource libvirt_network") + virConn := meta.(*Client).libvirt if virConn == nil { return fmt.Errorf(LibVirtConIsNil) @@ -530,6 +535,14 @@ func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error d.Set("name", networkDef.Name) d.Set("bridge", networkDef.Bridge.Name) + if networkDef.MTU != nil { + d.Set("mtu", networkDef.MTU.Size) + } + + if networkDef.Forward != nil { + d.Set("mode", networkDef.Forward.Mode) + } + // Domain as won't be present for bridged networks if networkDef.Domain != nil { d.Set("domain", networkDef.Domain.Name) @@ -541,6 +554,8 @@ func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error reading network autostart setting: %s", err) } d.Set("autostart", autostart) + + // read add the IP addresses 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) @@ -562,6 +577,17 @@ func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error d.Set("addresses", addresses) } + // set as DHCP=enabled if at least one of the IPs has a DHCP configuration + dhcpEnabled := false + for _, address := range networkDef.IPs { + if address.DHCP != nil { + dhcpEnabled = true + break + } + } + d.Set("dhcp.0.enabled", dhcpEnabled) + + // read the DNS configuration if networkDef.DNS != nil { for i, forwarder := range networkDef.DNS.Forwarders { key := fmt.Sprintf(dnsPrefix+".forwarders.%d", i) @@ -573,6 +599,18 @@ func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error } } } + + // and the static routes + if len(networkDef.Routes) > 0 { + for i, route := range networkDef.Routes { + routePrefix := fmt.Sprintf("routes.%d", i) + d.Set(routePrefix+".gateway", route.Gateway) + + cidr := fmt.Sprintf("%s/%d", route.Address, route.Prefix) + d.Set(routePrefix+".cidr", cidr) + } + } + // TODO: get any other parameters from the network and save them log.Printf("[DEBUG] Network ID %s successfully read", d.Id()) diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_volume.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_volume.go index c66cf5fc5f2..5c9f1e026e6 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_volume.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/resource_libvirt_volume.go @@ -164,24 +164,25 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error volumeDef.Capacity.Unit = "B" volumeDef.Capacity.Value = size } else { + // the volume does not have a source image to upload - // the volume does not have a source image to upload, first handle - // whether it has a backing image - // + // if size is given, set it to the specified value + if _, ok := d.GetOk("size"); ok { + volumeDef.Capacity.Value = uint64(d.Get("size").(int)) + } + + //first handle whether it has a backing image // backing images can be specified by either (id), or by (name, pool) var baseVolume *libvirt.StorageVol - if baseVolumeID, ok := d.GetOk("base_volume_id"); ok { if _, ok := d.GetOk("base_volume_name"); ok { return fmt.Errorf("'base_volume_name' can't be specified when also 'base_volume_id' is given") } baseVolume, err = client.libvirt.LookupStorageVolByKey(baseVolumeID.(string)) if err != nil { - return fmt.Errorf("Can't retrieve volume %s: %v", baseVolumeID.(string), err) + return fmt.Errorf("Can't retrieve volume ID '%s': %v", baseVolumeID.(string), err) } - } - - if baseVolumeName, ok := d.GetOk("base_volume_name"); ok { + } else if baseVolumeName, ok := d.GetOk("base_volume_name"); ok { baseVolumePool := pool if _, ok := d.GetOk("base_volume_pool"); ok { baseVolumePoolName := d.Get("base_volume_pool").(string) @@ -193,33 +194,35 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error } baseVolume, err = baseVolumePool.LookupStorageVolByName(baseVolumeName.(string)) if err != nil { - return fmt.Errorf("Can't retrieve volume %s: %v", baseVolumeName.(string), err) + return fmt.Errorf("Can't retrieve base volume with name '%s': %v", baseVolumeName.(string), err) } } if baseVolume != nil { - backingStoreDef, err := newDefBackingStoreFromLibvirt(baseVolume) + backingStoreFragmentDef, err := newDefBackingStoreFromLibvirt(baseVolume) if err != nil { return fmt.Errorf("Could not retrieve backing store definition: %s", err.Error()) } - // does the backing store have some size information?, check at least that it is not smaller than the backing store - volumeDef.Capacity.Value = uint64(d.Get("size").(int)) - if _, ok := d.GetOk("size"); ok { - backingStoreVolumeDef, err := newDefVolumeFromLibvirt(baseVolume) - if err != nil { - return err - } + backingStoreVolumeDef, err := newDefVolumeFromLibvirt(baseVolume) + if err != nil { + return err + } - if backingStoreVolumeDef.Capacity != nil && volumeDef.Capacity.Value < backingStoreVolumeDef.Capacity.Value { - return fmt.Errorf("When 'size' is specified, it shouldn't be smaller than the backing store specified with 'base_volume_id' or 'base_volume_name/base_volume_pool'") - } + // if the volume does not specify size, set it to the size of the backing store + if _, ok := d.GetOk("size"); !ok { + volumeDef.Capacity.Value = backingStoreVolumeDef.Capacity.Value } - volumeDef.BackingStore = &backingStoreDef + + // Always check that the size, specified or taken from the backing store + // is at least the size of the backing store itself + if backingStoreVolumeDef.Capacity != nil && volumeDef.Capacity.Value < backingStoreVolumeDef.Capacity.Value { + return fmt.Errorf("When 'size' is specified, it shouldn't be smaller than the backing store specified with 'base_volume_id' or 'base_volume_name/base_volume_pool'") + } + volumeDef.BackingStore = &backingStoreFragmentDef } } - volumeDef.Capacity.Value = uint64(d.Get("size").(int)) data, err := xmlMarshallIndented(volumeDef) if err != nil { return fmt.Errorf("Error serializing libvirt volume: %s", err) @@ -261,9 +264,14 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error } } + if err := volumeWaitForExists(client.libvirt, key); err != nil { + return err + } + return resourceLibvirtVolumeRead(d, meta) } +// resourceLibvirtVolumeRead returns the current state for a volume resource func resourceLibvirtVolumeRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*Client) virConn := client.libvirt @@ -271,7 +279,7 @@ func resourceLibvirtVolumeRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf(LibVirtConIsNil) } - volume, err := lookupVolumeReallyHard(client, d.Get("pool").(string), d.Id()) + volume, err := volumeLookupReallyHard(client, d.Get("pool").(string), d.Id()) if err != nil { return err } @@ -323,21 +331,23 @@ func resourceLibvirtVolumeRead(d *schema.ResourceData, meta interface{}) error { return nil } +// resourceLibvirtVolumeDelete removed a volume resource func resourceLibvirtVolumeDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*Client) if client.libvirt == nil { return fmt.Errorf(LibVirtConIsNil) } - return removeVolume(client, d.Id()) + return volumeDelete(client, d.Id()) } +// resourceLibvirtVolumeExists returns True if the volume resource exists func resourceLibvirtVolumeExists(d *schema.ResourceData, meta interface{}) (bool, error) { log.Printf("[DEBUG] Check if resource libvirt_volume exists") client := meta.(*Client) volPoolName := d.Get("pool").(string) - volume, err := lookupVolumeReallyHard(client, volPoolName, d.Id()) + volume, err := volumeLookupReallyHard(client, volPoolName, d.Id()) if err != nil { return false, err } diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/stream.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/stream.go index 4223466cc71..41274de0fcc 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/stream.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/stream.go @@ -19,8 +19,3 @@ func (sio *StreamIO) Read(p []byte) (int, error) { func (sio *StreamIO) Write(p []byte) (int, error) { return sio.Stream.Send(p) } - -// Close closes the stream -func (sio *StreamIO) Close() error { - return sio.Stream.Finish() -} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_net.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_net.go index 3e80fde2a14..cc67d0c70f8 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_net.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_net.go @@ -16,8 +16,10 @@ const ( ) // randomMACAddress returns a randomized MAC address +// with libvirt prefix func randomMACAddress() (string, error) { - buf := make([]byte, 6) + buf := make([]byte, 3) + rand.Seed(time.Now().UnixNano()) _, err := rand.Read(buf) if err != nil { return "", err @@ -33,8 +35,8 @@ func randomMACAddress() (string, error) { buf[0] = 0xee } - return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", - buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]), nil + return fmt.Sprintf("52:54:00:%02x:%02x:%02x", + buf[0], buf[1], buf[2]), nil } // randomPort returns a random port diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_volume.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_volume.go index 04afbeaec38..b3773e90312 100644 --- a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_volume.go +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/utils_volume.go @@ -3,229 +3,14 @@ package libvirt import ( "fmt" "io" - "io/ioutil" "log" - "net/http" - "net/url" - "os" "strconv" "strings" "time" - libvirt "github.com/libvirt/libvirt-go" - "github.com/libvirt/libvirt-go-xml" + "github.com/libvirt/libvirt-go" ) -// network transparent image -type image interface { - Size() (uint64, error) - Import(func(io.Reader) error, libvirtxml.StorageVolume) error - String() string - IsQCOW2() (bool, error) -} - -type localImage struct { - path string -} - -func (i *localImage) String() string { - return i.path -} - -func (i *localImage) Size() (uint64, error) { - file, err := os.Open(i.path) - if err != nil { - return 0, err - } - - fi, err := file.Stat() - if err != nil { - return 0, err - } - return uint64(fi.Size()), nil -} - -func (i *localImage) IsQCOW2() (bool, error) { - file, err := os.Open(i.path) - defer file.Close() - if err != nil { - return false, fmt.Errorf("Error while opening %s: %s", i.path, err) - } - buf := make([]byte, 8) - _, err = io.ReadAtLeast(file, buf, 8) - if err != nil { - return false, err - } - return isQCOW2Header(buf) -} - -func (i *localImage) Import(copier func(io.Reader) error, vol libvirtxml.StorageVolume) error { - file, err := os.Open(i.path) - defer file.Close() - if err != nil { - return fmt.Errorf("Error while opening %s: %s", i.path, err) - } - - fi, err := file.Stat() - if err != nil { - return err - } - // we can skip the upload if the modification times are the same - if vol.Target.Timestamps != nil && vol.Target.Timestamps.Mtime != "" { - if fi.ModTime() == timeFromEpoch(vol.Target.Timestamps.Mtime) { - log.Printf("Modification time is the same: skipping image copy") - return nil - } - } - - return copier(file) -} - -type httpImage struct { - url *url.URL -} - -func (i *httpImage) String() string { - return i.url.String() -} - -func (i *httpImage) Size() (uint64, error) { - response, err := http.Head(i.url.String()) - if err != nil { - return 0, err - } - if response.StatusCode == 403 { - // possibly only the HEAD method is forbidden, try a Body-less GET instead - response, err = http.Get(i.url.String()) - if err != nil { - return 0, err - } - - response.Body.Close() - } - if response.StatusCode != 200 { - return 0, - fmt.Errorf( - "Error accessing remote resource: %s - %s", - i.url.String(), - response.Status) - } - - length, err := strconv.Atoi(response.Header.Get("Content-Length")) - if err != nil { - err = fmt.Errorf( - "Error while getting Content-Length of \"%s\": %s - got %s", - i.url.String(), - err, - response.Header.Get("Content-Length")) - return 0, err - } - return uint64(length), nil -} - -func (i *httpImage) IsQCOW2() (bool, error) { - client := &http.Client{} - req, _ := http.NewRequest("GET", i.url.String(), nil) - req.Header.Set("Range", "bytes=0-7") - response, err := client.Do(req) - - if err != nil { - return false, err - } - defer response.Body.Close() - - if response.StatusCode != 206 { - return false, fmt.Errorf( - "Can't retrieve partial header of resource to determine file type: %s - %s", - i.url.String(), - response.Status) - } - - header, err := ioutil.ReadAll(response.Body) - if err != nil { - return false, err - } - - if len(header) < 8 { - return false, fmt.Errorf( - "Can't retrieve read header of resource to determine file type: %s - %d bytes read", - i.url.String(), - len(header)) - } - - return isQCOW2Header(header) -} - -// Whether the buffer is the header of a qcow2 file -func isQCOW2Header(buf []byte) (bool, error) { - if len(buf) < 8 { - return false, fmt.Errorf("Expected header of 8 bytes. Got %d", len(buf)) - } - if buf[0] == 'Q' && buf[1] == 'F' && buf[2] == 'I' && buf[3] == 0xfb && buf[4] == 0x00 && buf[5] == 0x00 && buf[6] == 0x00 && buf[7] == 0x03 { - return true, nil - } - return false, nil -} - -func (i *httpImage) Import(copier func(io.Reader) error, vol libvirtxml.StorageVolume) error { - // number of download retries on non client errors (eg. 5xx) - const maxHTTPRetries int = 3 - // wait time between retries - const retryWait time.Duration = 2 * time.Second - - client := &http.Client{} - req, err := http.NewRequest("GET", i.url.String(), nil) - - if err != nil { - log.Printf("[DEBUG:] Error creating new request for source url %s: %s", i.url.String(), err) - return fmt.Errorf("Error while downloading %s: %s", i.url.String(), err) - } - - if vol.Target.Timestamps != nil && vol.Target.Timestamps.Mtime != "" { - req.Header.Set("If-Modified-Since", timeFromEpoch(vol.Target.Timestamps.Mtime).UTC().Format(http.TimeFormat)) - } - - var response *http.Response - for retryCount := 0; retryCount < maxHTTPRetries; retryCount++ { - response, err = client.Do(req) - if err != nil { - return fmt.Errorf("Error while downloading %s: %v", i.url.String(), err) - } - defer response.Body.Close() - - log.Printf("[DEBUG]: url resp status code %s (retry #%d)\n", response.Status, retryCount) - if response.StatusCode == http.StatusNotModified { - return nil - } else if response.StatusCode == http.StatusOK { - return copier(response.Body) - } else if response.StatusCode < 500 { - break - } else { - // The problem is not client but server side - // retry a few times after a small wait - if retryCount < maxHTTPRetries { - time.Sleep(retryWait) - } - } - } - return fmt.Errorf("Error while downloading %s: %v", i.url.String(), response) -} - -func newImage(source string) (image, error) { - url, err := url.Parse(source) - if err != nil { - return nil, fmt.Errorf("Can't parse source '%s' as url: %s", source, err) - } - - if strings.HasPrefix(url.Scheme, "http") { - return &httpImage{url: url}, nil - } else if url.Scheme == "file" || url.Scheme == "" { - return &localImage{path: url.Path}, nil - } else { - return nil, fmt.Errorf("Don't know how to read from '%s': %s", url.String(), err) - } -} - func newCopier(virConn *libvirt.Connect, volume *libvirt.StorageVol, size uint64) func(src io.Reader) error { copier := func(src io.Reader) error { var bytesCopied int64 @@ -236,11 +21,6 @@ func newCopier(virConn *libvirt.Connect, volume *libvirt.StorageVol, size uint64 } defer func() { - if uint64(bytesCopied) != size { - stream.Abort() - } else { - stream.Finish() - } stream.Free() }() @@ -255,12 +35,24 @@ func newCopier(virConn *libvirt.Connect, volume *libvirt.StorageVol, size uint64 // if we get unexpected EOF this mean that connection was closed suddently from server side // the problem is not on the plugin but on server hosting currupted images if err == io.ErrUnexpectedEOF { + stream.Abort() return fmt.Errorf("Error: transfer was unexpectedly closed from the server while downloading. Please try again later or check the server hosting sources") } if err != nil { + stream.Abort() return fmt.Errorf("Error while copying source to volume %s", err) } + log.Printf("%d bytes uploaded\n", bytesCopied) + if uint64(bytesCopied) != size { + stream.Abort() + return fmt.Errorf("Error during volume Upload. BytesCopied: %d != %d volume.size", bytesCopied, size) + } + + if err := stream.Finish(); err != nil { + stream.Abort() + return fmt.Errorf("Error by terminating libvirt stream %s", err) + } return nil } return copier @@ -277,99 +69,3 @@ func timeFromEpoch(str string) time.Time { return time.Unix(int64(s), int64(ns)) } - -// removeVolume removes the volume identified by `key` from libvirt -func removeVolume(client *Client, key string) error { - volume, err := client.libvirt.LookupStorageVolByKey(key) - if err != nil { - return fmt.Errorf("Can't retrieve volume %s: %v", key, err) - } - defer volume.Free() - - // Refresh the pool of the volume so that libvirt knows it is - // not longer in use. - volPool, err := volume.LookupPoolByVolume() - if err != nil { - return fmt.Errorf("Error retrieving pool for volume: %s", err) - } - defer volPool.Free() - - poolName, err := volPool.GetName() - if err != nil { - return fmt.Errorf("Error retrieving name of volume: %s", err) - } - - client.poolMutexKV.Lock(poolName) - defer client.poolMutexKV.Unlock(poolName) - - waitForSuccess("Error refreshing pool for volume", func() error { - return volPool.Refresh(0) - }) - - // Workaround for redhat#1293804 - // https://bugzilla.redhat.com/show_bug.cgi?id=1293804#c12 - // Does not solve the problem but it makes it happen less often. - _, err = volume.GetXMLDesc(0) - if err != nil { - return fmt.Errorf("Can't retrieve volume %s XML desc: %s", key, err) - } - - err = volume.Delete(0) - if err != nil { - return fmt.Errorf("Can't delete volume %s: %s", key, err) - } - - return nil -} - -// tries really hard to find volume with `key` -// it will try to start the pool if it does not find it -// -// You have to call volume.Free() on the returned volume -func lookupVolumeReallyHard(client *Client, volPoolName string, key string) (*libvirt.StorageVol, error) { - virConn := client.libvirt - if virConn == nil { - return nil, fmt.Errorf(LibVirtConIsNil) - } - - volume, err := virConn.LookupStorageVolByKey(key) - if err != nil { - virErr := err.(libvirt.Error) - if virErr.Code != libvirt.ERR_NO_STORAGE_VOL { - return nil, fmt.Errorf("Can't retrieve volume %s", key) - } - log.Printf("[INFO] Volume %s not found, attempting to start its pool", key) - - volPool, err := virConn.LookupStoragePoolByName(volPoolName) - if err != nil { - return nil, fmt.Errorf("Error retrieving pool %s for volume %s: %s", volPoolName, key, err) - } - defer volPool.Free() - - active, err := volPool.IsActive() - if err != nil { - return nil, fmt.Errorf("error retrieving status of pool %s for volume %s: %s", volPoolName, key, err) - } - if active { - log.Printf("Can't retrieve volume %s (and pool is active)", key) - return nil, nil - } - - err = volPool.Create(0) - if err != nil { - return nil, fmt.Errorf("error starting pool %s: %s", volPoolName, err) - } - - // attempt a new lookup - volume, err = virConn.LookupStorageVolByKey(key) - if err != nil { - virErr := err.(libvirt.Error) - if virErr.Code != libvirt.ERR_NO_STORAGE_VOL { - return nil, fmt.Errorf("Can't retrieve volume %s", key) - } - // does not exist, but no error - return nil, nil - } - } - return volume, nil -} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/volume.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/volume.go new file mode 100644 index 00000000000..dfdcf0ff676 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/volume.go @@ -0,0 +1,163 @@ +package libvirt + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/libvirt/libvirt-go" +) + +const ( + volExistsID = "EXISTS" + volNotExistsID = "NOT-EXISTS" +) + +// volumeExists returns "EXISTS" or "NOT-EXISTS" depending on the current volume existence +func volumeExists(virConn *libvirt.Connect, key string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + vol, err := virConn.LookupStorageVolByKey(key) + if err != nil { + if err.(libvirt.Error).Code == libvirt.ERR_NO_STORAGE_VOL { + log.Printf("Volume %s does not exist", key) + return virConn, "NOT-EXISTS", nil + } + log.Printf("Volume %s: error: %s", key, err.(libvirt.Error).Message) + } + defer vol.Free() + return virConn, volExistsID, err + } +} + +// volumeWaitForExists waits for a storage volume to be up and timeout after 5 minutes. +func volumeWaitForExists(virConn *libvirt.Connect, key string) error { + log.Printf("Waiting for volume %s to be active...", key) + stateConf := &resource.StateChangeConf{ + Pending: []string{volNotExistsID}, + Target: []string{volExistsID}, + Refresh: volumeExists(virConn, key), + Timeout: 1 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for volume to reach EXISTS state: %s", err) + } + return nil +} + +// volumeWaitDeleted waits for a storage volume to be removed +func volumeWaitDeleted(virConn *libvirt.Connect, key string) error { + log.Printf("Waiting for volume %s to be deleted...", key) + stateConf := &resource.StateChangeConf{ + Pending: []string{volExistsID}, + Target: []string{volNotExistsID}, + Refresh: volumeExists(virConn, key), + Timeout: 1 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for volume to reach NOT-EXISTS state: %s", err) + } + return nil +} + +// volumeDelete removes the volume identified by `key` from libvirt +func volumeDelete(client *Client, key string) error { + volume, err := client.libvirt.LookupStorageVolByKey(key) + if err != nil { + return fmt.Errorf("Can't retrieve volume %s: %v", key, err) + } + defer volume.Free() + + // Refresh the pool of the volume so that libvirt knows it is + // not longer in use. + volPool, err := volume.LookupPoolByVolume() + if err != nil { + return fmt.Errorf("Error retrieving pool for volume: %s", err) + } + defer volPool.Free() + + poolName, err := volPool.GetName() + if err != nil { + return fmt.Errorf("Error retrieving name of volume: %s", err) + } + + client.poolMutexKV.Lock(poolName) + defer client.poolMutexKV.Unlock(poolName) + + waitForSuccess("Error refreshing pool for volume", func() error { + return volPool.Refresh(0) + }) + + // Workaround for redhat#1293804 + // https://bugzilla.redhat.com/show_bug.cgi?id=1293804#c12 + // Does not solve the problem but it makes it happen less often. + _, err = volume.GetXMLDesc(0) + if err != nil { + return fmt.Errorf("Can't retrieve volume %s XML desc: %s", key, err) + } + + err = volume.Delete(0) + if err != nil { + return fmt.Errorf("Can't delete volume %s: %s", key, err) + } + + return volumeWaitDeleted(client.libvirt, key) +} + +// tries really hard to find volume with `key` +// it will try to start the pool if it does not find it +// +// You have to call volume.Free() on the returned volume +func volumeLookupReallyHard(client *Client, volPoolName string, key string) (*libvirt.StorageVol, error) { + virConn := client.libvirt + if virConn == nil { + return nil, fmt.Errorf(LibVirtConIsNil) + } + + volume, err := virConn.LookupStorageVolByKey(key) + if err != nil { + virErr := err.(libvirt.Error) + if virErr.Code != libvirt.ERR_NO_STORAGE_VOL { + return nil, fmt.Errorf("Can't retrieve volume %s", key) + } + log.Printf("[INFO] Volume %s not found, attempting to start its pool", key) + + volPool, err := virConn.LookupStoragePoolByName(volPoolName) + if err != nil { + return nil, fmt.Errorf("Error retrieving pool %s for volume %s: %s", volPoolName, key, err) + } + defer volPool.Free() + + active, err := volPool.IsActive() + if err != nil { + return nil, fmt.Errorf("error retrieving status of pool %s for volume %s: %s", volPoolName, key, err) + } + if active { + log.Printf("Can't retrieve volume %s (and pool is active)", key) + return nil, nil + } + + err = volPool.Create(0) + if err != nil { + return nil, fmt.Errorf("error starting pool %s: %s", volPoolName, err) + } + + // attempt a new lookup + volume, err = virConn.LookupStorageVolByKey(key) + if err != nil { + virErr := err.(libvirt.Error) + if virErr.Code != libvirt.ERR_NO_STORAGE_VOL { + return nil, fmt.Errorf("Can't retrieve volume %s", key) + } + // does not exist, but no error + return nil, nil + } + } + return volume, nil +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/volume_image.go b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/volume_image.go new file mode 100644 index 00000000000..418491d3aef --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/dmacvicar/terraform-provider-libvirt/libvirt/volume_image.go @@ -0,0 +1,226 @@ +package libvirt + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/libvirt/libvirt-go-xml" +) + +// network transparent image +type image interface { + Size() (uint64, error) + Import(func(io.Reader) error, libvirtxml.StorageVolume) error + String() string + IsQCOW2() (bool, error) +} + +type localImage struct { + path string +} + +func (i *localImage) String() string { + return i.path +} + +func (i *localImage) Size() (uint64, error) { + file, err := os.Open(i.path) + if err != nil { + return 0, err + } + + fi, err := file.Stat() + if err != nil { + return 0, err + } + return uint64(fi.Size()), nil +} + +func (i *localImage) IsQCOW2() (bool, error) { + file, err := os.Open(i.path) + defer file.Close() + if err != nil { + return false, fmt.Errorf("Error while opening %s: %s", i.path, err) + } + buf := make([]byte, 8) + _, err = io.ReadAtLeast(file, buf, 8) + if err != nil { + return false, err + } + return isQCOW2Header(buf) +} + +func (i *localImage) Import(copier func(io.Reader) error, vol libvirtxml.StorageVolume) error { + file, err := os.Open(i.path) + defer file.Close() + if err != nil { + return fmt.Errorf("Error while opening %s: %s", i.path, err) + } + + fi, err := file.Stat() + if err != nil { + return err + } + // we can skip the upload if the modification times are the same + if vol.Target.Timestamps != nil && vol.Target.Timestamps.Mtime != "" { + if fi.ModTime() == timeFromEpoch(vol.Target.Timestamps.Mtime) { + log.Printf("Modification time is the same: skipping image copy") + return nil + } + } + + return copier(file) +} + +type httpImage struct { + url *url.URL +} + +func (i *httpImage) String() string { + return i.url.String() +} + +func (i *httpImage) Size() (uint64, error) { + response, err := http.Head(i.url.String()) + if err != nil { + return 0, err + } + if response.StatusCode == 403 { + // possibly only the HEAD method is forbidden, try a Body-less GET instead + response, err = http.Get(i.url.String()) + if err != nil { + return 0, err + } + + response.Body.Close() + } + if response.StatusCode != 200 { + return 0, + fmt.Errorf( + "Error accessing remote resource: %s - %s", + i.url.String(), + response.Status) + } + + length, err := strconv.Atoi(response.Header.Get("Content-Length")) + if err != nil { + err = fmt.Errorf( + "Error while getting Content-Length of \"%s\": %s - got %s", + i.url.String(), + err, + response.Header.Get("Content-Length")) + return 0, err + } + return uint64(length), nil +} + +func (i *httpImage) IsQCOW2() (bool, error) { + client := &http.Client{} + req, _ := http.NewRequest("GET", i.url.String(), nil) + req.Header.Set("Range", "bytes=0-7") + response, err := client.Do(req) + + if err != nil { + return false, err + } + defer response.Body.Close() + + if response.StatusCode != 206 { + return false, fmt.Errorf( + "Can't retrieve partial header of resource to determine file type: %s - %s", + i.url.String(), + response.Status) + } + + header, err := ioutil.ReadAll(response.Body) + if err != nil { + return false, err + } + + if len(header) < 8 { + return false, fmt.Errorf( + "Can't retrieve read header of resource to determine file type: %s - %d bytes read", + i.url.String(), + len(header)) + } + + return isQCOW2Header(header) +} + +func (i *httpImage) Import(copier func(io.Reader) error, vol libvirtxml.StorageVolume) error { + // number of download retries on non client errors (eg. 5xx) + const maxHTTPRetries int = 3 + // wait time between retries + const retryWait time.Duration = 2 * time.Second + + client := &http.Client{} + req, err := http.NewRequest("GET", i.url.String(), nil) + + if err != nil { + log.Printf("[DEBUG:] Error creating new request for source url %s: %s", i.url.String(), err) + return fmt.Errorf("Error while downloading %s: %s", i.url.String(), err) + } + + if vol.Target.Timestamps != nil && vol.Target.Timestamps.Mtime != "" { + req.Header.Set("If-Modified-Since", timeFromEpoch(vol.Target.Timestamps.Mtime).UTC().Format(http.TimeFormat)) + } + + var response *http.Response + for retryCount := 0; retryCount < maxHTTPRetries; retryCount++ { + response, err = client.Do(req) + if err != nil { + return fmt.Errorf("Error while downloading %s: %v", i.url.String(), err) + } + defer response.Body.Close() + + log.Printf("[DEBUG]: url resp status code %s (retry #%d)\n", response.Status, retryCount) + if response.StatusCode == http.StatusNotModified { + return nil + } else if response.StatusCode == http.StatusOK { + return copier(response.Body) + } else if response.StatusCode < 500 { + break + } else { + // The problem is not client but server side + // retry a few times after a small wait + if retryCount < maxHTTPRetries { + time.Sleep(retryWait) + } + } + } + return fmt.Errorf("Error while downloading %s: %v", i.url.String(), response) +} + +func newImage(source string) (image, error) { + url, err := url.Parse(source) + if err != nil { + return nil, fmt.Errorf("Can't parse source '%s' as url: %s", source, err) + } + + if strings.HasPrefix(url.Scheme, "http") { + return &httpImage{url: url}, nil + } else if url.Scheme == "file" || url.Scheme == "" { + return &localImage{path: url.Path}, nil + } else { + return nil, fmt.Errorf("Don't know how to read from '%s': %s", url.String(), err) + } +} + +// isQCOW2Header returns True when the buffer starts with the qcow2 header +func isQCOW2Header(buf []byte) (bool, error) { + if len(buf) < 8 { + return false, fmt.Errorf("Expected header of 8 bytes. Got %d", len(buf)) + } + if buf[0] == 'Q' && buf[1] == 'F' && buf[2] == 'I' && buf[3] == 0xfb && buf[4] == 0x00 && buf[5] == 0x00 && buf[6] == 0x00 && buf[7] == 0x03 { + return true, nil + } + return false, nil +}