aboutsummaryrefslogtreecommitdiff
path: root/libvirt
diff options
context:
space:
mode:
authorAlvaro Saurin <alvaro.saurin@gmail.com>2016-06-27 12:06:29 +0200
committerAlvaro Saurin <alvaro.saurin@gmail.com>2016-07-01 22:34:40 +0200
commitf6a859c2f91d3adef137270879e936271adaaf01 (patch)
tree147bdc7bf5fdd8f1a688a5f5a5927fec1063db74 /libvirt
parente9301b5a3a18c93f6a00bfad857ef0474b548928 (diff)
downloadterraform-provider-libvirt-f6a859c2f91d3adef137270879e936271adaaf01.tar
terraform-provider-libvirt-f6a859c2f91d3adef137270879e936271adaaf01.tar.gz
Improved network resource
Methods for adding/removing hosts to a network Style, formatting improvements and fixes
Diffstat (limited to 'libvirt')
-rw-r--r--libvirt/network_def.go145
-rw-r--r--libvirt/network_def_test.go93
-rw-r--r--libvirt/network_interface_def.go73
-rw-r--r--libvirt/provider.go1
-rw-r--r--libvirt/resource_libvirt_domain.go339
-rw-r--r--libvirt/resource_libvirt_domain_netiface.go68
-rw-r--r--libvirt/resource_libvirt_domain_test.go5
-rw-r--r--libvirt/resource_libvirt_network.go331
-rw-r--r--libvirt/utils.go32
-rw-r--r--libvirt/utils_libvirt.go41
-rw-r--r--libvirt/utils_net.go58
-rw-r--r--libvirt/utils_test.go16
12 files changed, 1060 insertions, 142 deletions
diff --git a/libvirt/network_def.go b/libvirt/network_def.go
new file mode 100644
index 00000000..eb263f08
--- /dev/null
+++ b/libvirt/network_def.go
@@ -0,0 +1,145 @@
+package libvirt
+
+import (
+ "encoding/xml"
+ "fmt"
+
+ libvirt "github.com/dmacvicar/libvirt-go"
+)
+
+type defNetworkIpDhcpRange struct {
+ XMLName xml.Name `xml:"range,omitempty"`
+
+ Start string `xml:"start,attr,omitempty"`
+ End string `xml:"end,attr,omitempty"`
+}
+
+type defNetworkIpDhcpHost struct {
+ XMLName xml.Name `xml:"host,omitempty"`
+
+ Ip string `xml:"ip,attr,omitempty"`
+ Mac string `xml:"mac,attr,omitempty"`
+ Name string `xml:"name,attr,omitempty"`
+}
+
+type defNetworkIpDhcp struct {
+ XMLName xml.Name `xml:"dhcp,omitempty"`
+
+ Ranges []*defNetworkIpDhcpRange `xml:"range,omitempty"`
+ Hosts []*defNetworkIpDhcpHost `xml:"host,omitempty"`
+}
+
+type defNetworkIp struct {
+ XMLName xml.Name `xml:"ip,omitempty"`
+
+ Address string `xml:"address,attr"`
+ Netmask string `xml:"netmask,attr,omitempty"`
+ Prefix int `xml:"prefix,attr,omitempty"`
+ Family string `xml:"family,attr,omitempty"`
+ Dhcp *defNetworkIpDhcp `xml:"dhcp,omitempty"`
+}
+
+type defNetworkBridge struct {
+ XMLName xml.Name `xml:"bridge,omitempty"`
+
+ Name string `xml:"name,attr,omitempty"`
+ Stp string `xml:"stp,attr,omitempty"`
+}
+
+type defNetworkDomain struct {
+ XMLName xml.Name `xml:"domain,omitempty"`
+
+ Name string `xml:"name,attr,omitempty"`
+ LocalOnly string `xml:"localOnly,attr,omitempty"`
+}
+
+type defNetworkForward struct {
+ Mode string `xml:"mode,attr"`
+ Device string `xml:"dev,attr,omitempty"`
+ Nat *struct {
+ Addresses []*struct {
+ Start string `xml:"start,attr"`
+ End string `xml:"end,attr"`
+ } `xml:"address,omitempty"`
+ Ports []*struct {
+ Start string `xml:"start,attr"`
+ End string `xml:"end,attr"`
+ } `xml:"port,omitempty"`
+ } `xml:"nat,omitempty"`
+}
+
+type defNetworkDns struct {
+ Host []*struct {
+ Ip string `xml:"ip,attr"`
+ HostName []string `xml:"hostname"`
+ } `xml:"host,omitempty"`
+ Forwarder []*struct {
+ Address string `xml:"addr,attr"`
+ } `xml:"forwarder,omitempty"`
+}
+
+// network definition in XML, compatible with what libvirt expects
+// note: we have to use pointers or otherwise golang's XML will not properly detect
+// empty values and generate things like "<bridge></bridge>" that
+// make libvirt crazy...
+type defNetwork struct {
+ XMLName xml.Name `xml:"network"`
+
+ Name string `xml:"name,omitempty"`
+ Domain *defNetworkDomain `xml:"domain,omitempty"`
+ Bridge *defNetworkBridge `xml:"bridge,omitempty"`
+ Forward *defNetworkForward `xml:"forward,omitempty"`
+ Ips []*defNetworkIp `xml:"ip,omitempty"`
+ Dns *defNetworkDns `xml:"dns,omitempty"`
+}
+
+// Check if the network has a DHCP server managed by libvirt
+func (net defNetwork) HasDHCP() bool {
+ if net.Forward != nil {
+ if net.Forward.Mode == "nat" || net.Forward.Mode == "route" || net.Forward.Mode == "" {
+ return true
+ }
+ }
+ return false
+}
+
+// Creates a network definition from a XML
+func newDefNetworkFromXML(s string) (defNetwork, error) {
+ var networkDef defNetwork
+ err := xml.Unmarshal([]byte(s), &networkDef)
+ if err != nil {
+ return defNetwork{}, err
+ }
+ return networkDef, nil
+}
+
+func newDefNetworkfromLibvirt(network *libvirt.VirNetwork) (defNetwork, error) {
+ networkXmlDesc, err := network.GetXMLDesc(0)
+ if err != nil {
+ return defNetwork{}, fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
+ }
+ networkDef := defNetwork{}
+ err = xml.Unmarshal([]byte(networkXmlDesc), &networkDef)
+ if err != nil {
+ return defNetwork{}, fmt.Errorf("Error reading libvirt network XML description: %s", err)
+ }
+ return networkDef, nil
+}
+
+// Creates a network definition with the defaults the provider uses
+func newNetworkDef() defNetwork {
+ const defNetworkXML = `
+ <network>
+ <name>default</name>
+ <forward mode='nat'>
+ <nat>
+ <port start='1024' end='65535'/>
+ </nat>
+ </forward>
+ </network>`
+ if d, err := newDefNetworkFromXML(defNetworkXML); err != nil {
+ panic(fmt.Sprint("Unexpected error while parsing default network definition: %s", err))
+ } else {
+ return d
+ }
+}
diff --git a/libvirt/network_def_test.go b/libvirt/network_def_test.go
new file mode 100644
index 00000000..cd2048c7
--- /dev/null
+++ b/libvirt/network_def_test.go
@@ -0,0 +1,93 @@
+package libvirt
+
+import (
+ "bytes"
+ "encoding/xml"
+ "testing"
+
+ "github.com/davecgh/go-spew/spew"
+)
+
+func init() {
+ spew.Config.Indent = "\t"
+}
+
+func TestDefaultNetworkMarshall(t *testing.T) {
+ b := newNetworkDef()
+ prettyB := spew.Sdump(b)
+ t.Logf("Parsed default network:\n%s", prettyB)
+
+ buf := new(bytes.Buffer)
+ enc := xml.NewEncoder(buf)
+ enc.Indent(" ", " ")
+ if err := enc.Encode(b); err != nil {
+ t.Fatalf("could not marshall this:\n%s", spew.Sdump(b))
+ }
+ t.Logf("Marshalled default network:\n%s", buf.String())
+}
+
+func TestNetworkDefUnmarshall(t *testing.T) {
+ // some testing XML from the official docs (some unsupported attrs will be just ignored)
+ text := `
+ <network>
+ <name>my-network</name>
+ <bridge name="virbr0" stp="on" delay="5" macTableManager="libvirt"/>
+ <mac address='00:16:3E:5D:C7:9E'/>
+ <domain name="example.com" localOnly="no"/>
+ <forward mode='nat'>
+ <nat>
+ <address start='1.2.3.4' end='1.2.3.10'/>
+ </nat>
+ </forward>
+ <dns>
+ <txt name="example" value="example value" />
+ <forwarder addr="8.8.8.8"/>
+ <forwarder addr="8.8.4.4"/>
+ <srv service='name' protocol='tcp' domain='test-domain-name' target='.' port='1024' priority='10' weight='10'/>
+ <host ip='192.168.122.2'>
+ <hostname>myhost</hostname>
+ <hostname>myhostalias</hostname>
+ </host>
+ </dns>
+ <ip address="192.168.122.1" netmask="255.255.255.0">
+ <dhcp>
+ <range start="192.168.122.100" end="192.168.122.254" />
+ <host mac="00:16:3e:77:e2:ed" name="foo.example.com" ip="192.168.122.10" />
+ <host mac="00:16:3e:3e:a9:1a" name="bar.example.com" ip="192.168.122.11" />
+ </dhcp>
+ </ip>
+ <ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64" />
+ <route family="ipv6" address="2001:db9:ca1:1::" prefix="64" gateway="2001:db8:ca2:2::2" />
+ </network>
+ `
+
+ b, err := newDefNetworkFromXML(text)
+ prettyB := spew.Sdump(b)
+ t.Logf("Parsed:\n%s", prettyB)
+ if err != nil {
+ t.Errorf("could not parse: %s", err)
+ }
+ if b.Name != "my-network" {
+ t.Errorf("wrong network name: '%s'", b.Name)
+ }
+ if b.Domain.Name != "example.com" {
+ t.Errorf("wrong domain name: '%s'", b.Domain.Name)
+ }
+ if b.Forward.Mode != "nat" {
+ t.Errorf("wrong forward mode: '%s'", b.Forward.Mode)
+ }
+ if len(b.Forward.Nat.Addresses) == 0 {
+ t.Errorf("wrong number of addresses: %s", b.Forward.Nat.Addresses)
+ }
+ if b.Forward.Nat.Addresses[0].Start != "1.2.3.4" {
+ t.Errorf("wrong forward start address: %s", b.Forward.Nat.Addresses[0].Start)
+ }
+ if len(b.Ips) == 0 {
+ t.Errorf("wrong number of IPs: %d", len(b.Ips))
+ }
+ if bs, err := xmlMarshallIndented(b); err != nil {
+ t.Fatalf("marshalling error\n%s", spew.Sdump(b))
+ } else {
+ t.Logf("Marshalled:\n%s", bs)
+ }
+}
diff --git a/libvirt/network_interface_def.go b/libvirt/network_interface_def.go
index 289211f7..20a4c248 100644
--- a/libvirt/network_interface_def.go
+++ b/libvirt/network_interface_def.go
@@ -2,9 +2,16 @@ package libvirt
import (
"encoding/xml"
- "github.com/hashicorp/terraform/helper/schema"
)
+// An interface definition, as returned/understood by libvirt
+// (see https://libvirt.org/formatdomain.html#elementsNICS)
+//
+// Something like:
+// <interface type='network'>
+// <source network='default'/>
+// </interface>
+//
type defNetworkInterface struct {
XMLName xml.Name `xml:"interface"`
Type string `xml:"type,attr"`
@@ -13,68 +20,12 @@ type defNetworkInterface struct {
} `xml:"mac"`
Source struct {
Network string `xml:"network,attr"`
- } `xml:"source"`
+ Bridge string `xml:"bridge,attr"`
+ Dev string `xml:"dev,attr"`
+ Mode string `xml:"mode,attr"`
+ } `xml:"source"`
Model struct {
Type string `xml:"type,attr"`
} `xml:"model"`
waitForLease bool
}
-
-func networkAddressCommonSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "type": &schema.Schema{
- Type: schema.TypeString,
- Optional: true,
- Computed: true,
- },
- "address": &schema.Schema{
- Type: schema.TypeString,
- Optional: true,
- Computed: true,
- },
- "prefix": &schema.Schema{
- Type: schema.TypeInt,
- Optional: true,
- Computed: true,
- },
- }
-}
-
-func networkInterfaceCommonSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "network": &schema.Schema{
- Type: schema.TypeString,
- Optional: true,
- Default: "default",
- ForceNew: true,
- },
- "mac": &schema.Schema{
- Type: schema.TypeString,
- Optional: true,
- Computed: true,
- ForceNew: true,
- },
- "wait_for_lease": &schema.Schema{
- Type: schema.TypeBool,
- Optional: true,
- },
- "address": &schema.Schema{
- Type: schema.TypeList,
- Optional: true,
- Computed: true,
- Elem: &schema.Resource{
- Schema: networkAddressCommonSchema(),
- },
- },
- }
-}
-
-func newDefNetworkInterface() defNetworkInterface {
- iface := defNetworkInterface{}
- iface.Type = "network"
- //iface.Mac.Address = "52:54:00:36:c0:65"
- iface.Source.Network = "default"
- iface.Model.Type = "virtio"
- iface.waitForLease = false
- return iface
-}
diff --git a/libvirt/provider.go b/libvirt/provider.go
index 5290816e..b0f86054 100644
--- a/libvirt/provider.go
+++ b/libvirt/provider.go
@@ -19,6 +19,7 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"libvirt_domain": resourceLibvirtDomain(),
"libvirt_volume": resourceLibvirtVolume(),
+ "libvirt_network": resourceLibvirtNetwork(),
"libvirt_cloudinit": resourceCloudInit(),
},
diff --git a/libvirt/resource_libvirt_domain.go b/libvirt/resource_libvirt_domain.go
index a0aff83e..67c95b38 100644
--- a/libvirt/resource_libvirt_domain.go
+++ b/libvirt/resource_libvirt_domain.go
@@ -3,18 +3,27 @@ package libvirt
import (
"encoding/xml"
"fmt"
- libvirt "github.com/dmacvicar/libvirt-go"
- "github.com/hashicorp/terraform/helper/schema"
"log"
+ "net"
+ "strings"
"time"
+
+ "github.com/davecgh/go-spew/spew"
+ libvirt "github.com/dmacvicar/libvirt-go"
+ "github.com/hashicorp/terraform/helper/schema"
)
+func init() {
+ spew.Config.Indent = "\t"
+}
+
func resourceLibvirtDomain() *schema.Resource {
return &schema.Resource{
Create: resourceLibvirtDomainCreate,
Read: resourceLibvirtDomainRead,
Delete: resourceLibvirtDomainDelete,
Update: resourceLibvirtDomainUpdate,
+ Exists: resourceLibvirtDomainExists,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
@@ -64,7 +73,6 @@ func resourceLibvirtDomain() *schema.Resource {
Type: schema.TypeList,
Optional: true,
Required: false,
- ForceNew: true,
Elem: &schema.Resource{
Schema: networkInterfaceCommonSchema(),
},
@@ -73,12 +81,34 @@ func resourceLibvirtDomain() *schema.Resource {
}
}
+func resourceLibvirtDomainExists(d *schema.ResourceData, meta interface{}) (bool, error) {
+ virConn := meta.(*Client).libvirt
+ if virConn == nil {
+ return false, fmt.Errorf("The libvirt connection was nil.")
+ }
+ domain, err := virConn.LookupByUUIDString(d.Id())
+ defer domain.Free()
+ return err == nil, err
+}
+
func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error {
virConn := meta.(*Client).libvirt
if virConn == nil {
return fmt.Errorf("The libvirt connection was nil.")
}
+ domainDef := newDomainDef()
+ if name, ok := d.GetOk("name"); ok {
+ domainDef.Name = name.(string)
+ }
+
+ if metadata, ok := d.GetOk("metadata"); ok {
+ domainDef.Metadata.TerraformLibvirt.Xml = metadata.(string)
+ }
+
+ domainDef.Memory.Amount = d.Get("memory").(int)
+ domainDef.VCpu.Amount = d.Get("vcpu").(int)
+
disksCount := d.Get("disk.#").(int)
var disks []defDisk
for i := 0; i < disksCount; i++ {
@@ -110,6 +140,12 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error
disks = append(disks, disk)
}
+ type pendingMapping struct {
+ mac string
+ hostname string
+ network *libvirt.VirNetwork
+ }
+
if cloudinit, ok := d.GetOk("cloudinit"); ok {
disk, err := newDiskForCloudInit(virConn, cloudinit.(string))
if err != nil {
@@ -120,42 +156,116 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error
netIfacesCount := d.Get("network_interface.#").(int)
netIfaces := make([]defNetworkInterface, 0, netIfacesCount)
+ partialNetIfaces := make(map[string]pendingMapping, netIfacesCount)
for i := 0; i < netIfacesCount; i++ {
prefix := fmt.Sprintf("network_interface.%d", i)
- netIface := newDefNetworkInterface()
- if mac, ok := d.GetOk(prefix + ".mac"); ok {
- netIface.Mac.Address = mac.(string)
- } else {
+ netIface := defNetworkInterface{}
+ netIface.Model.Type = "virtio"
+
+ // calculate the MAC address
+ macI, ok := d.GetOk(prefix + ".mac")
+ mac := strings.ToUpper(macI.(string))
+ if !ok {
var err error
- netIface.Mac.Address, err = RandomMACAddress()
+ mac, err = RandomMACAddress()
if err != nil {
return fmt.Errorf("Error generating mac address: %s", err)
}
}
+ netIface.Mac.Address = mac
// this is not passed to libvirt, but used by waitForAddress
+ netIface.waitForLease = false
if waitForLease, ok := d.GetOk(prefix + ".wait_for_lease"); ok {
netIface.waitForLease = waitForLease.(bool)
}
- if network, ok := d.GetOk(prefix + ".network"); ok {
- netIface.Source.Network = network.(string)
- }
- netIfaces = append(netIfaces, netIface)
- }
+ // connect to the interface to the network... first, look for the network
+ if n, ok := d.GetOk(prefix + ".network_name"); ok {
+ // when using a "network_name" we do not try to do anything: we just
+ // connect to that network
+ netIface.Type = "network"
+ netIface.Source.Network = n.(string)
+ } else if networkUUID, ok := d.GetOk(prefix + ".network_id"); ok {
+ // when using a "network_id" we are referring to a "network resource"
+ // we have defined somewhere else...
+ network, err := virConn.LookupNetworkByUUIDString(networkUUID.(string))
+ if err != nil {
+ return fmt.Errorf("Can't retrieve network ID %s", networkUUID)
+ }
+ defer network.Free()
- domainDef := newDomainDef()
- if name, ok := d.GetOk("name"); ok {
- domainDef.Name = name.(string)
- }
+ networkName, err := network.GetName()
+ if err != nil {
+ return fmt.Errorf("Error retrieving volume name: %s", err)
+ }
+ networkDef, err := newDefNetworkfromLibvirt(&network)
+ if !networkDef.HasDHCP() {
+ continue
+ }
- if metadata, ok := d.GetOk("metadata"); ok {
- domainDef.Metadata.TerraformLibvirt.Xml = metadata.(string)
+ hostname := domainDef.Name
+ if hostnameI, ok := d.GetOk(prefix + ".hostname"); ok {
+ hostname = hostnameI.(string)
+ }
+ if addresses, ok := d.GetOk("addresses"); ok {
+ // some IP(s) provided
+ for _, addressI := range addresses.([]interface{}) {
+ address := addressI.(string)
+ ip := net.ParseIP(address)
+ if ip == nil {
+ return fmt.Errorf("Could not parse addresses '%s'", address)
+ }
+ // TODO: we should check the IP is contained in the DHCP addresses served
+ log.Printf("[INFO] Adding IP/MAC/host=%s/%s/%s to %s", ip.String(), mac, hostname, networkName)
+ if err := addHost(&network, ip.String(), mac, hostname); err != nil {
+ return err
+ }
+ }
+ } else {
+ // no IPs provided: if the hostname has been provided, wait until we get an IP
+ if len(hostname) > 0 {
+ if !netIface.waitForLease {
+ return fmt.Errorf("Cannot map '%s': we are not waiting for 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] Will wait for an IP for hostname '%s'...", hostname)
+ partialNetIfaces[mac] = pendingMapping{
+ mac: mac,
+ hostname: hostname,
+ network: &network,
+ }
+ } else {
+ // neither an IP or a hostname has been provided: so nothing must be forced
+ }
+ }
+ netIface.Type = "network"
+ netIface.Source.Network = networkName
+ } else if bridgeNameI, ok := d.GetOk(prefix + ".bridge"); ok {
+ netIface.Type = "bridge"
+ netIface.Source.Bridge = bridgeNameI.(string)
+ } else if devI, ok := d.GetOk(prefix + ".vepa"); ok {
+ netIface.Type = "direct"
+ netIface.Source.Dev = devI.(string)
+ netIface.Source.Mode = "vepa"
+ } else if devI, ok := d.GetOk(prefix + ".macvtap"); ok {
+ netIface.Type = "direct"
+ netIface.Source.Dev = devI.(string)
+ netIface.Source.Mode = "bridge"
+ } else if devI, ok := d.GetOk(prefix + ".passthrough"); ok {
+ netIface.Type = "direct"
+ netIface.Source.Dev = devI.(string)
+ netIface.Source.Mode = "passthrough"
+ } else {
+ // no network has been specified: we are on our own
+ }
+
+ netIfaces = append(netIfaces, netIface)
}
- domainDef.Memory.Amount = d.Get("memory").(int)
- domainDef.VCpu.Amount = d.Get("vcpu").(int)
domainDef.Devices.Disks = disks
domainDef.Devices.NetworkInterfaces = netIfaces
@@ -165,11 +275,13 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error
}
log.Printf("[INFO] Creating libvirt domain at %s", connectURI)
- data, err := xml.Marshal(domainDef)
+ data, err := xmlMarshallIndented(domainDef)
if err != nil {
return fmt.Errorf("Error serializing libvirt domain: %s", err)
}
+ log.Printf("[DEBUG] Creating libvirt domain with XML:\n%s", string(data))
+
domain, err := virConn.DomainDefineXML(string(data))
if err != nil {
return fmt.Errorf("Error defining libvirt domain: %s", err)
@@ -199,7 +311,37 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error
return err
}
- return resourceLibvirtDomainRead(d, meta)
+ err = resourceLibvirtDomainRead(d, meta)
+ if err != nil {
+ return err
+ }
+
+ // we must read devices again in order to set some missing ip/MAC/host mappings
+ for i := 0; i < netIfacesCount; i++ {
+ prefix := fmt.Sprintf("network_interface.%d", i)
+
+ macI := d.Get(prefix + ".mac")
+ mac := strings.ToUpper(macI.(string))
+
+ // if we were waiting for an IP address for this MAC, go ahead.
+ if pending, ok := partialNetIfaces[mac]; ok {
+ // we should have the address now
+ if addressesI, ok := d.GetOk(prefix + ".addresses"); !ok {
+ return fmt.Errorf("Did not obtain the IP address for MAC=%s", mac)
+ } else {
+ for _, addressI := range addressesI.([]interface{}) {
+ address := addressI.(string)
+ log.Printf("[INFO] Finally adding IP/MAC/host=%s/%s/%s", address, mac, pending.hostname)
+ addHost(pending.network, address, mac, pending.hostname)
+ if err != nil {
+ return fmt.Errorf("Could not add IP/MAC/host=%s/%s/%s: %s", address, mac, pending.hostname, err)
+ }
+ }
+ }
+ }
+ }
+
+ return nil
}
func resourceLibvirtDomainUpdate(d *schema.ResourceData, meta interface{}) error {
@@ -207,7 +349,6 @@ func resourceLibvirtDomainUpdate(d *schema.ResourceData, meta interface{}) error
if virConn == nil {
return fmt.Errorf("The libvirt connection was nil.")
}
-
domain, err := virConn.LookupByUUIDString(d.Id())
if err != nil {
return fmt.Errorf("Error retrieving libvirt domain: %s", err)
@@ -263,6 +404,42 @@ func resourceLibvirtDomainUpdate(d *schema.ResourceData, meta interface{}) error
}
}
+ netIfacesCount := d.Get("network_interface.#").(int)
+ for i := 0; i < netIfacesCount; i++ {
+ prefix := fmt.Sprintf("network_interface.%d", i)
+ if d.HasChange(prefix+".hostname") || d.HasChange(prefix+".addresses") || d.HasChange(prefix+".mac") {
+ networkUUID, ok := d.GetOk(prefix + ".network_id")
+ if !ok {
+ continue
+ }
+ network, err := virConn.LookupNetworkByUUIDString(networkUUID.(string))
+ if err != nil {
+ return fmt.Errorf("Can't retrieve network ID %s", networkUUID)
+ }
+ defer network.Free()
+
+ networkName, err := network.GetName()
+ if err != nil {
+ return fmt.Errorf("Error retrieving volume name: %s", err)
+ }
+ hostname := d.Get(prefix + ".hostname").(string)
+ mac := d.Get(prefix + ".mac").(string)
+ addresses := d.Get(prefix + ".addresses")
+ for _, addressI := range addresses.([]interface{}) {
+ address := addressI.(string)
+ ip := net.ParseIP(address)
+ if ip == nil {
+ return fmt.Errorf("Could not parse addresses '%s'", address)
+ }
+ log.Printf("[INFO] Updating IP/MAC/host=%s/%s/%s in '%s' network", ip.String(), mac, hostname, networkName)
+ if err := updateHost(&network, ip.String(), mac, hostname); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ // TODO
return nil
}
@@ -316,7 +493,7 @@ func resourceLibvirtDomainRead(d *schema.ResourceData, meta interface{}) error {
virVolKey, err := virVol.GetKey()
if err != nil {
- return fmt.Errorf("Error retrieving volume ke for disk: %s", err)
+ return fmt.Errorf("Error retrieving volume for disk: %s", err)
}
disk := map[string]interface{}{
@@ -345,51 +522,88 @@ func resourceLibvirtDomainRead(d *schema.ResourceData, meta interface{}) error {
// we need it to read old values
prefix := fmt.Sprintf("network_interface.%d", i)
- if networkInterfaceDef.Type != "network" {
- log.Printf("[DEBUG] ignoring interface of type '%s'", networkInterfaceDef.Type)
- continue
+ netIface := map[string]interface{}{
+ "network_id": "",
+ "network_name": "",
+ "bridge": "",
+ "vepa": "",
+ "macvtap": "",
+ "passthrough": "",
+ "mac": strings.ToUpper(networkInterfaceDef.Mac.Address),
+ "hostname": "",
+ "wait_for_lease": false,
}
- netIface := map[string]interface{}{
- "network": networkInterfaceDef.Source.Network,
- "mac": networkInterfaceDef.Mac.Address,
- }
-
- netIfaceAddrs := make([]map[string]interface{}, 0)
- // look for an ip address and try to match it with the mac address
- // not sure if using the target device name is a better idea here
- for _, ifaceWithAddr := range ifacesWithAddr {
- if ifaceWithAddr.Hwaddr == networkInterfaceDef.Mac.Address {
- for _, addr := range ifaceWithAddr.Addrs {
- netIfaceAddr := map[string]interface{}{
- "type": func() string {
- switch addr.Type {
- case libvirt.VIR_IP_ADDR_TYPE_IPV4:
- return "ipv4"
- case libvirt.VIR_IP_ADDR_TYPE_IPV6:
- return "ipv6"
- default:
- return "other"
+ switch networkInterfaceDef.Type {
+ case "network": {
+ network, err := virConn.LookupNetworkByName(networkInterfaceDef.Source.Network)
+ if err != nil {
+ return fmt.Errorf("Can't retrieve network ID for '%s'", networkInterfaceDef.Source.Network)
+ }
+ defer network.Free()
+
+ netIface["network_id"], err = network.GetUUIDString()
+ if err != nil {
+ return fmt.Errorf("Can't retrieve network ID for '%s'", networkInterfaceDef.Source.Network)
+ }
+
+ networkDef, err := newDefNetworkfromLibvirt(&network)
+ if err != nil {
+ return err
+ }
+
+ netIface["network_name"] = networkInterfaceDef.Source.Network
+
+ // try to look for this MAC in the DHCP configuration for this VM
+ if networkDef.HasDHCP() {
+ hostnameSearch:
+ for _, ip := range networkDef.Ips {
+ if ip.Dhcp != nil {
+ for _, host := range ip.Dhcp.Hosts {
+ if strings.ToUpper(host.Mac) == netIface["mac"] {
+ log.Printf("[DEBUG] read: hostname for '%s': '%s'", netIface["mac"], host.Name)
+ netIface["hostname"] = host.Name
+ break hostnameSearch
}
- }(),
- "address": addr.Addr,
- "prefix": addr.Prefix,
+ }
}
- netIfaceAddrs = append(netIfaceAddrs, netIfaceAddr)
}
}
- }
- log.Printf("[DEBUG] %d addresses for %s\n", len(netIfaceAddrs), networkInterfaceDef.Mac.Address)
- netIface["address"] = netIfaceAddrs
+ // look for an ip address and try to match it with the mac address
+ // not sure if using the target device name is a better idea here
+ addrs := make([]string, 0)
+ for _, ifaceWithAddr := range ifacesWithAddr {
+ if strings.ToUpper(ifaceWithAddr.Hwaddr) == netIface["mac"] {
+ for _, addr := range ifaceWithAddr.Addrs {
+ addrs = append(addrs, addr.Addr)
+ }
+ }
+ }
+ netIface["addresses"] = addrs
+ log.Printf("[DEBUG] read: addresses for '%s': %+v", netIface["mac"], addrs)
+
+ netIface["wait_for_lease"] = d.Get(prefix + ".wait_for_lease").(bool)
- // pass on old wait_for_lease value
- if waitForLease, ok := d.GetOk(prefix + ".wait_for_lease"); ok {
- netIface["wait_for_lease"] = waitForLease
+ }
+ case "bridge":
+ netIface["bridge"] = networkInterfaceDef.Source.Bridge
+ case "direct":
+ {
+ switch networkInterfaceDef.Source.Mode {
+ case "vepa":
+ netIface["vepa"] = networkInterfaceDef.Source.Dev
+ case "bridge":
+ netIface["macvtap"] = networkInterfaceDef.Source.Dev
+ case "passthrough":
+ netIface["passthrough"] = networkInterfaceDef.Source.Dev
+ }
+ }
}
netIfaces = append(netIfaces, netIface)
}
+ log.Printf("[DEBUG] read: ifaces for '%s':\n%s", domainDef.Name, spew.Sdump(netIfaces))
d.Set("network_interface", netIfaces)
if len(ifacesWithAddr) > 0 {
@@ -407,6 +621,8 @@ func resourceLibvirtDomainDelete(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("The libvirt connection was nil.")
}
+ log.Printf("[DEBUG] Deleting domain %s", d.Id())
+
domain, err := virConn.LookupByUUIDString(d.Id())
if err != nil {
return fmt.Errorf("Error retrieving libvirt domain: %s", err)
@@ -463,7 +679,8 @@ func waitForNetworkAddresses(ifaces []defNetworkInterface, domain libvirt.VirDom
continue
}
- if iface.Mac.Address == "" {
+ mac := strings.ToUpper(iface.Mac.Address)
+ if mac == "" {
log.Printf("[DEBUG] Can't wait without a mac address.\n")
// we can't get the ip without a mac address
continue
@@ -471,6 +688,7 @@ func waitForNetworkAddresses(ifaces []defNetworkInterface, domain libvirt.VirDom
// loop until address appear, with timeout
start := time.Now()
+
waitLoop:
for {
log.Printf("[DEBUG] waiting for network address for interface with hwaddr: '%s'\n", iface.Mac.Address)
@@ -478,10 +696,11 @@ func waitForNetworkAddresses(ifaces []defNetworkInterface, domain libvirt.VirDom
if err != nil {
return fmt.Errorf("Error retrieving interface addresses: %s", err)
}
+ log.Printf("[DEBUG] ifaces with addresses: %+v\n", ifacesWithAddr)
for _, ifaceWithAddr := range ifacesWithAddr {
// found
- if iface.Mac.Address == ifaceWithAddr.Hwaddr {
+ if mac == strings.ToUpper(ifaceWithAddr.Hwaddr) {
break waitLoop
}
}
diff --git a/libvirt/resource_libvirt_domain_netiface.go b/libvirt/resource_libvirt_domain_netiface.go
new file mode 100644
index 00000000..88c5e69b
--- /dev/null
+++ b/libvirt/resource_libvirt_domain_netiface.go
@@ -0,0 +1,68 @@
+package libvirt
+
+import (
+ "github.com/hashicorp/terraform/helper/schema"
+)
+
+func networkInterfaceCommonSchema() map[string]*schema.Schema {
+ return map[string]*schema.Schema{
+ "network_id": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Computed: true,
+ },
+ "network_name": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Computed: true,
+ },
+ "bridge": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ },
+ "vepa": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ },
+ "macvtap": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Computed: true,
+ },
+ "passthrough": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ },
+ "hostname": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ForceNew: false,
+ },
+ "mac": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ForceNew: true,
+ },
+ "wait_for_lease": &schema.Schema{
+ Type: schema.TypeBool,
+ Optional: true,
+ },
+ "addresses": &schema.Schema{
+ Type: schema.TypeList,
+ Optional: true,
+ Computed: true,
+ ForceNew: false,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ }
+}
diff --git a/libvirt/resource_libvirt_domain_test.go b/libvirt/resource_libvirt_domain_test.go
index be778355..cabbad0b 100644
--- a/libvirt/resource_libvirt_domain_test.go
+++ b/libvirt/resource_libvirt_domain_test.go
@@ -2,12 +2,13 @@ package libvirt
import (
"fmt"
+ "log"
+ "testing"
+
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
//"gopkg.in/alexzorin/libvirt-go.v2"
libvirt "github.com/dmacvicar/libvirt-go"
- "log"
- "testing"
)
func TestAccLibvirtDomain_Basic(t *testing.T) {
diff --git a/libvirt/resource_libvirt_network.go b/libvirt/resource_libvirt_network.go
new file mode 100644
index 00000000..063a274c
--- /dev/null
+++ b/libvirt/resource_libvirt_network.go
@@ -0,0 +1,331 @@
+package libvirt
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "strings"
+ "time"
+
+ libvirt "github.com/dmacvicar/libvirt-go"
+ "github.com/hashicorp/terraform/helper/resource"
+ "github.com/hashicorp/terraform/helper/schema"
+)
+
+const (
+ netModeIsolated = "none"
+ netModeNat = "nat"
+ netModeRoute = "route"
+ netModeBridge = "bridge"
+)
+
+// a libvirt network resource
+//
+// Resource example:
+//
+// resource "libvirt_network" "k8snet" {
+// name = "k8snet"
+// domain = "k8s.local"
+// mode = "nat"
+// addresses = ["10.17.3.0/24"]
+// }
+//
+// "addresses" can contain (0 or 1) ipv4 and (0 or 1) ipv6 ranges
+// "mode" can be one of: "nat" (default), "isolated"
+//
+func resourceLibvirtNetwork() *schema.Resource {
+ return &schema.Resource{
+ Create: resourceLibvirtNetworkCreate,
+ Read: resourceLibvirtNetworkRead,
+ Delete: resourceLibvirtNetworkDelete,
+ Exists: resourceLibvirtNetworkExists,
+ Schema: map[string]*schema.Schema{
+ "name": &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "domain": &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "mode": &schema.Schema{ // can be "none", "nat" (default), "route", "bridge"
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Default: netModeNat,
+ },
+ "bridge": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ForceNew: true,
+ },
+ "addresses": &schema.Schema{
+ Type: schema.TypeList,
+ Optional: true,
+ Required: false,
+ ForceNew: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ }
+}
+
+func resourceLibvirtNetworkExists(d *schema.ResourceData, meta interface{}) (bool, error) {
+ virConn := meta.(*Client).libvirt
+ if virConn == nil {
+ return false, fmt.Errorf("The libvirt connection was nil.")
+ }
+ network, err := virConn.LookupNetworkByUUIDString(d.Id())
+ defer network.Free()
+ return err == nil, err
+}
+
+func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) error {
+ // see https://libvirt.org/formatnetwork.html
+ virConn := meta.(*Client).libvirt
+ if virConn == nil {
+ return fmt.Errorf("The libvirt connection was nil.")
+ }
+
+ networkDef := newNetworkDef()
+ networkDef.Name = d.Get("name").(string)
+ networkDef.Domain = &defNetworkDomain{
+ Name: d.Get("domain").(string),
+ }
+
+ // 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 = &defNetworkBridge{
+ Name: bridgeName,
+ Stp: "on",
+ }
+
+ // check the network mode
+ networkDef.Forward.Mode = strings.ToLower(d.Get("mode").(string))
+ if networkDef.Forward.Mode == netModeIsolated || networkDef.Forward.Mode == netModeNat || networkDef.Forward.Mode == netModeRoute {
+
+ // there is no mode when using an isolated network
+ if networkDef.Forward.Mode == netModeIsolated {
+ networkDef.Forward = nil
+ }
+
+ // some network modes require a DHCP/DNS server
+ // set the addresses for DHCP
+ if addresses, ok := d.GetOk("addresses"); ok {
+ ipsPtrsLst := []*defNetworkIp{}
+ for _, addressI := range addresses.([]interface{}) {
+ address := addressI.(string)
+ _, ipNet, err := net.ParseCIDR(address)
+ if err != nil {
+ return 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 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 := defNetworkIp{
+ Address: start.String(),
+ Prefix: ones,
+ Family: family,
+ }
+
+ start[len(start)-1]++ // then skip the .1
+ end[len(end)-1]-- // and skip the .255 (for broadcast)
+
+ dni.Dhcp = &defNetworkIpDhcp{
+ Ranges: []*defNetworkIpDhcpRange{
+ &defNetworkIpDhcpRange{
+ Start: start.String(),
+ End: end.String(),
+ },
+ },
+ }
+ ipsPtrsLst = append(ipsPtrsLst, &dni)
+ }
+ networkDef.Ips = ipsPtrsLst
+ }
+ } else if networkDef.Forward.Mode == netModeBridge {
+ if bridgeName == "" {
+ return fmt.Errorf("'bridge' must be provided when using the bridged network mode")
+ }
+ } else {
+ return fmt.Errorf("unsuppoorted network mode '%s'", networkDef.Forward.Mode)
+ }
+
+ // once we have the network defined, connect to libvirt and create it from the XML serialization
+ connectURI, err := virConn.GetURI()
+ if err != nil {
+ return fmt.Errorf("Error retrieving libvirt connection URI: %s", err)
+ }
+ log.Printf("[INFO] Creating libvirt network at %s", connectURI)
+
+ data, err := xmlMarshallIndented(networkDef)
+ if err != nil {
+ return fmt.Errorf("Error serializing libvirt network: %s", err)
+ }
+
+ log.Printf("[DEBUG] Creating libvirt network at %s: %s", connectURI, data)
+ network, err := virConn.NetworkDefineXML(data)
+ if err != nil {
+ return fmt.Errorf("Error defining libvirt network: %s - %s", err, data)
+ }
+ err = network.Create()
+ if err != nil {
+ return fmt.Errorf("Error crearing libvirt network: %s", err)
+ }
+ defer network.Free()
+
+ id, err := network.GetUUIDString()
+ if err != nil {
+ return fmt.Errorf("Error retrieving libvirt network id: %s", err)
+ }
+ d.SetId(id)
+
+ log.Printf("[INFO] Created network %s [%s]", networkDef.Name, d.Id())
+
+ stateConf := &resource.StateChangeConf{
+ Pending: []string{"BUILD"},
+ Target: []string{"ACTIVE"},
+ Refresh: waitForNetworkActive(network),
+ Timeout: 1 * time.Minute,
+ Delay: 5 * time.Second,
+ MinTimeout: 3 * time.Second,
+ }
+ _, err = stateConf.WaitForState()
+ if err != nil {
+ return fmt.Errorf("Error waiting for network to reach ACTIVE state: %s", err)
+ }
+
+ return resourceLibvirtNetworkRead(d, meta)
+}
+
+func resourceLibvirtNetworkRead(d *schema.ResourceData, meta interface{}) error {
+ virConn := meta.(*Client).libvirt
+ if virConn == nil {
+ return fmt.Errorf("The libvirt connection was nil.")
+ }
+
+ network, err := virConn.LookupNetworkByUUIDString(d.Id())
+ if err != nil {
+ return fmt.Errorf("Error retrieving libvirt network: %s", err)
+ }
+ defer network.Free()
+
+ networkDef, err := newDefNetworkfromLibvirt(&network)
+ if err != nil {
+ return fmt.Errorf("Error reading libvirt network XML description: %s", err)
+ }
+
+ d.Set("name", networkDef.Name)
+ d.Set("domain", networkDef.Domain.Name)
+ d.Set("bridge", networkDef.Bridge.Name)
+
+ 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)
+ // so we need some transformations...
+ addr := net.ParseIP(address.Address)
+ if addr == nil {
+ return fmt.Errorf("Error parsing IP '%s': %s", address, err)
+ }
+ bits := net.IPv6len * 8
+ if addr.To4() != nil {
+ bits = net.IPv4len * 8
+ }
+ mask := net.CIDRMask(address.Prefix, bits)
+ network := addr.Mask(mask)
+ addresses = append(addresses, fmt.Sprintf("%s/%d", network, address.Prefix))
+ }
+ if len(addresses) > 0 {
+ d.Set("addresses", addresses)
+ }
+
+ // TODO: get any other parameters from the network and save them
+
+ log.Printf("[DEBUG] Network ID %s successfully read", d.Id())
+ return nil
+}
+
+func resourceLibvirtNetworkDelete(d *schema.ResourceData, meta interface{}) error {
+ virConn := meta.(*Client).libvirt
+ if virConn == nil {
+ return fmt.Errorf("The libvirt connection was nil.")
+ }
+ log.Printf("[DEBUG] Deleting network ID %s", d.Id())
+
+ network, err := virConn.LookupNetworkByUUIDString(d.Id())
+ if err != nil {
+ return fmt.Errorf("When destroying libvirt network: error retrieving %s", err)
+ }
+ defer network.Free()
+
+ if err := network.Destroy(); err != nil {
+ return fmt.Errorf("When destroying libvirt network: %s", err)
+ }
+
+ if err := network.Destroy(); err != nil {
+ return fmt.Errorf("When destroying libvirt network: %s", err)
+ }
+
+ stateConf := &resource.StateChangeConf{
+ Pending: []string{"ACTIVE"},
+ Target: []string{"NOT-EXISTS"},
+ Refresh: waitForNetworkDestroyed(virConn, d.Id()),
+ Timeout: 1 * time.Minute,
+ Delay: 5 * time.Second,
+ MinTimeout: 3 * time.Second,
+ }
+ _, err = stateConf.WaitForState()
+ if err != nil {
+ return fmt.Errorf("Error waiting for network to reach NOT-EXISTS state: %s", err)
+ }
+ return nil
+}
+
+func waitForNetworkActive(network libvirt.VirNetwork) resource.StateRefreshFunc {
+ return func() (interface{}, string, error) {
+ active, err := network.IsActive()
+ if err != nil {
+ return nil, "", err
+ }
+ if active {
+ return network, "ACTIVE", nil
+ }
+ return network, "BUILD", err
+ }
+}
+
+// wait for network to be up and timeout after 5 minutes.
+func waitForNetworkDestroyed(virConn *libvirt.VirConnection, uuid string) resource.StateRefreshFunc {
+ return func() (interface{}, string, error) {
+ log.Printf("Waiting for network %s to be destroyed", uuid)
+ network, err := virConn.LookupNetworkByUUIDString(uuid)
+ if err.(libvirt.VirError).Code == libvirt.VIR_ERR_NO_NETWORK {
+ return virConn, "NOT-EXISTS", nil
+ }
+ defer network.Free()
+ return virConn, "ACTIVE", err
+ }
+}
diff --git a/libvirt/utils.go b/libvirt/utils.go
index 1fc412c8..da1502f7 100644
--- a/libvirt/utils.go
+++ b/libvirt/utils.go
@@ -1,11 +1,14 @@
package libvirt
import (
- "crypto/rand"
+ "bytes"
+ "encoding/xml"
"fmt"
- libvirt "github.com/dmacvicar/libvirt-go"
"log"
"time"
+
+ libvirt "github.com/dmacvicar/libvirt-go"
+ "github.com/davecgh/go-spew/spew"
)
var diskLetters []rune = []rune("abcdefghijklmnopqrstuvwxyz")
@@ -40,24 +43,15 @@ func WaitForSuccess(errorMessage string, f func() error) error {
}
}
-func RandomMACAddress() (string, error) {
- buf := make([]byte, 6)
- _, err := rand.Read(buf)
- if err != nil {
- return "", err
- }
-
- // set local bit and unicast
- buf[0] = (buf[0] | 2) & 0xfe
- // Set the local bit
- buf[0] |= 2
-
- // avoid libvirt-reserved addresses
- if buf[0] == 0xfe {
- buf[0] = 0xee
+// return an indented XML
+func xmlMarshallIndented(b interface{}) (string, error) {
+ buf := new(bytes.Buffer)
+ enc := xml.NewEncoder(buf)
+ enc.Indent(" ", " ")
+ if err := enc.Encode(b); err != nil {
+ fmt.Errorf("could not marshall this:\n%s", spew.Sdump(b))
}
-
- return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]), nil
+ return buf.String(), nil
}
// Remove the volume identified by `key` from libvirt
diff --git a/libvirt/utils_libvirt.go b/libvirt/utils_libvirt.go
new file mode 100644
index 00000000..e14e9554
--- /dev/null
+++ b/libvirt/utils_libvirt.go
@@ -0,0 +1,41 @@
+package libvirt
+
+import (
+ "log"
+
+ libvirt "github.com/dmacvicar/libvirt-go"
+)
+
+func getHostXMLDesc(ip, mac, name string) string {
+ dd := defNetworkIpDhcpHost{
+ Ip: ip,
+ Mac: mac,
+ Name: name,
+ }
+ xml, err := xmlMarshallIndented(dd)
+ if err != nil {
+ panic("could not marshall host")
+ }
+ return xml
+}
+
+// Adds a new static host to the network
+func addHost(n *libvirt.VirNetwork, ip, mac, name string) error {
+ xmlDesc := getHostXMLDesc(ip, mac, name)
+ log.Printf("Adding host with XML:\n%s", xmlDesc)
+ return n.UpdateXMLDesc(xmlDesc, libvirt.VIR_NETWORK_UPDATE_COMMAND_ADD_LAST, libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST)
+}
+
+// Removes a static host from the network
+func removeHost(n *libvirt.VirNetwork, ip, mac, name string) error {
+ xmlDesc := getHostXMLDesc(ip, mac, name)
+ log.Printf("Removing host with XML:\n%s", xmlDesc)
+ return n.UpdateXMLDesc(xmlDesc, libvirt.VIR_NETWORK_UPDATE_COMMAND_DELETE, libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST)
+}
+
+// Update a static host from the network
+func updateHost(n *libvirt.VirNetwork, ip, mac, name string) error {
+ xmlDesc := getHostXMLDesc(ip, mac, name)
+ log.Printf("Updating host with XML:\n%s", xmlDesc)
+ return n.UpdateXMLDesc(xmlDesc, libvirt.VIR_NETWORK_UPDATE_COMMAND_MODIFY, libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST)
+}
diff --git a/libvirt/utils_net.go b/libvirt/utils_net.go
new file mode 100644
index 00000000..8f96d6a3
--- /dev/null
+++ b/libvirt/utils_net.go
@@ -0,0 +1,58 @@
+package libvirt
+
+import (
+ "crypto/rand"
+ "fmt"
+ "net"
+)
+
+const (
+ maxIfaceNum = 100
+)
+
+func RandomMACAddress() (string, error) {
+ buf := make([]byte, 6)
+ _, err := rand.Read(buf)
+ if err != nil {
+ return "", err
+ }
+
+ // set local bit and unicast
+ buf[0] = (buf[0] | 2) & 0xfe
+ // Set the local bit
+ buf[0] |= 2
+
+ // avoid libvirt-reserved addresses
+ if buf[0] == 0xfe {
+ 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
+}
+
+func FreeNetworkInterface(basename string) (string, error) {
+ for i := 0; i < maxIfaceNum; i++ {
+ ifaceName := fmt.Sprintf("%s%d", basename, i)
+ _, err := net.InterfaceByName(ifaceName)
+ if err != nil {
+ return ifaceName, nil
+ }
+ }
+ return "", fmt.Errorf("could not obtain a free network interface")
+}
+
+// Calculates the first and last IP addresses in an IPNet
+func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
+ netIP := network.IP.To4()
+ lastIP := net.IPv4(0, 0, 0, 0).To4()
+ if netIP == nil {
+ netIP = network.IP.To16()
+ lastIP = net.IPv6zero.To16()
+ }
+ firstIP := netIP.Mask(network.Mask)
+ for i := 0; i < len(lastIP); i++ {
+ lastIP[i] = netIP[i] | ^network.Mask[i]
+ }
+ return firstIP, lastIP
+}
diff --git a/libvirt/utils_test.go b/libvirt/utils_test.go
index 671964cf..62000f34 100644
--- a/libvirt/utils_test.go
+++ b/libvirt/utils_test.go
@@ -1,6 +1,7 @@
package libvirt
import (
+ "net"
"testing"
)
@@ -16,3 +17,18 @@ func TestDiskLetterForIndex(t *testing.T) {
}
}
}
+
+func TestIPsRange(t *testing.T) {
+ _, net, err := net.ParseCIDR("192.168.18.1/24")
+ if err != nil {
+ t.Errorf("When parsing network: %s", err)
+ }
+
+ start, end := NetworkRange(net)
+ if start.String() != "192.168.18.0" {
+ t.Errorf("unexpected range start for '%s': %s", net, start)
+ }
+ if end.String() != "192.168.18.255" {
+ t.Errorf("unexpected range start for '%s': %s", net, start)
+ }
+}