engine: util, resources: virt: Clean up virt resource

Do some cleanups which were long overdue.
This commit is contained in:
James Shubin
2019-03-15 15:24:40 -04:00
parent c4f57608d0
commit ae56261961
2 changed files with 296 additions and 100 deletions

View File

@@ -29,6 +29,7 @@ import (
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits" "github.com/purpleidea/mgmt/engine/traits"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
@@ -65,30 +66,53 @@ const (
// VirtRes is a libvirt resource. A transient virt resource, which has its state // VirtRes is a libvirt resource. A transient virt resource, which has its state
// set to `shutoff` is one which does not exist. The parallel equivalent is a // set to `shutoff` is one which does not exist. The parallel equivalent is a
// file resource which removes a particular path. // file resource which removes a particular path.
// TODO: some values inside here should be enum's!
type VirtRes struct { type VirtRes struct {
traits.Base // add the base methods without re-implementation traits.Base // add the base methods without re-implementation
traits.Refreshable traits.Refreshable
init *engine.Init init *engine.Init
URI string `lang:"uri" yaml:"uri"` // connection uri, eg: qemu:///session // URI is the libvirt connection URI, eg: `qemu:///session`.
State string `lang:"state" yaml:"state"` // running, paused, shutoff URI string `lang:"uri" yaml:"uri"`
Transient bool `lang:"transient" yaml:"transient"` // defined (false) or undefined (true) // State is the desired vm state. Possible values include: `running`,
CPUs uint `lang:"cpus" yaml:"cpus"` // `paused` and `shutoff`.
MaxCPUs uint `lang:"maxcpus" yaml:"maxcpus"` State string `lang:"state" yaml:"state"`
Memory uint64 `lang:"memory" yaml:"memory"` // in KBytes // Transient is whether the vm is defined (false) or undefined (true).
OSInit string `lang:"osinit" yaml:"osinit"` // init used by lxc Transient bool `lang:"transient" yaml:"transient"`
Boot []string `lang:"boot" yaml:"boot"` // boot order. values: fd, hd, cdrom, network
Disk []DiskDevice `lang:"disk" yaml:"disk"`
CDRom []CDRomDevice `lang:"cdrom" yaml:"cdrom"`
Network []NetworkDevice `lang:"network" yaml:"network"`
Filesystem []FilesystemDevice `lang:"filesystem" yaml:"filesystem"`
Auth *VirtAuth `lang:"auth" yaml:"auth"`
HotCPUs bool `lang:"hotcpus" yaml:"hotcpus"` // allow hotplug of cpus? // CPUs is the desired cpu count of the machine.
// FIXME: values here should be enum's! CPUs uint `lang:"cpus" yaml:"cpus"`
RestartOnDiverge string `lang:"restartondiverge" yaml:"restartondiverge"` // restart policy: "ignore", "ifneeded", "error" // MaxCPUs is the maximum number of cpus allowed in the machine. You
RestartOnRefresh bool `lang:"restartonrefresh" yaml:"restartonrefresh"` // restart on refresh? // need to set this so that on boot the `hardware` knows how many cpu
// `slots` it might need to make room for.
MaxCPUs uint `lang:"maxcpus" yaml:"maxcpus"`
// HotCPUs specifies whether we can hot plug and unplug cpus.
HotCPUs bool `lang:"hotcpus" yaml:"hotcpus"`
// Memory is the size in KBytes of memory to include in the machine.
Memory uint64 `lang:"memory" yaml:"memory"`
// OSInit is the init used by lxc.
OSInit string `lang:"osinit" yaml:"osinit"`
// Boot is the boot order. Values are `fd`, `hd`, `cdrom` and `network`.
Boot []string `lang:"boot" yaml:"boot"`
// Disk is the list of disk devices to include.
Disk []*DiskDevice `lang:"disk" yaml:"disk"`
// CdRom is the list of cdrom devices to include.
CDRom []*CDRomDevice `lang:"cdrom" yaml:"cdrom"`
// Network is the list of network devices to include.
Network []*NetworkDevice `lang:"network" yaml:"network"`
// Filesystem is the list of file system devices to include.
Filesystem []*FilesystemDevice `lang:"filesystem" yaml:"filesystem"`
// Auth points to the libvirt credentials to use if any are necessary.
Auth *VirtAuth `lang:"auth" yaml:"auth"`
// RestartOnDiverge is the restart policy, and can be: `ignore`,
// `ifneeded` or `error`.
RestartOnDiverge string `lang:"restartondiverge" yaml:"restartondiverge"`
// RestartOnRefresh specifies if we restart on refresh signal.
RestartOnRefresh bool `lang:"restartonrefresh" yaml:"restartonrefresh"`
wg *sync.WaitGroup wg *sync.WaitGroup
conn *libvirt.Connect conn *libvirt.Connect
@@ -107,6 +131,24 @@ type VirtAuth struct {
Password string `lang:"password" yaml:"password"` Password string `lang:"password" yaml:"password"`
} }
// Cmp compares two VirtAuth structs. It errors if they are not identical.
func (obj *VirtAuth) Cmp(auth *VirtAuth) error {
if (obj == nil) != (auth == nil) { // xor
return fmt.Errorf("the VirtAuth differs")
}
if obj == nil && auth == nil {
return nil
}
if obj.Username != auth.Username {
return fmt.Errorf("the Username differs")
}
if obj.Password != auth.Password {
return fmt.Errorf("the Password differs")
}
return nil
}
// Default returns some sensible defaults for this resource. // Default returns some sensible defaults for this resource.
func (obj *VirtRes) Default() engine.Res { func (obj *VirtRes) Default() engine.Res {
return &VirtRes{ return &VirtRes{
@@ -138,7 +180,7 @@ func (obj *VirtRes) Init(init *engine.Init) error {
var u *url.URL var u *url.URL
var err error var err error
if u, err = url.Parse(obj.URI); err != nil { if u, err = url.Parse(obj.URI); err != nil {
return errwrap.Wrapf(err, "%s: Parsing URI failed: %s", obj, obj.URI) return errwrap.Wrapf(err, "parsing URI (`%s`) failed", obj.URI)
} }
switch u.Scheme { switch u.Scheme {
case "lxc": case "lxc":
@@ -149,7 +191,7 @@ func (obj *VirtRes) Init(init *engine.Init) error {
obj.conn, err = obj.connect() // gets closed in Close method of Res API obj.conn, err = obj.connect() // gets closed in Close method of Res API
if err != nil { if err != nil {
return errwrap.Wrapf(err, "%s: Connection to libvirt failed in init", obj) return errwrap.Wrapf(err, "connection to libvirt failed in init")
} }
// check for hard to change properties // check for hard to change properties
@@ -157,14 +199,14 @@ func (obj *VirtRes) Init(init *engine.Init) error {
if err == nil { if err == nil {
defer dom.Free() defer dom.Free()
} else if !isNotFound(err) { } else if !isNotFound(err) {
return errwrap.Wrapf(err, "%s: Could not lookup on init", obj) return errwrap.Wrapf(err, "could not lookup on init")
} }
if err == nil { if err == nil {
// maxCPUs, err := dom.GetMaxVcpus() // maxCPUs, err := dom.GetMaxVcpus()
i, err := dom.GetVcpusFlags(libvirt.DOMAIN_VCPU_MAXIMUM) i, err := dom.GetVcpusFlags(libvirt.DOMAIN_VCPU_MAXIMUM)
if err != nil { if err != nil {
return errwrap.Wrapf(err, "%s: Could not lookup MaxCPUs on init", obj) return errwrap.Wrapf(err, "could not lookup MaxCPUs on init")
} }
maxCPUs := uint(i) maxCPUs := uint(i)
if obj.MaxCPUs != maxCPUs { // max cpu slots is hard to change if obj.MaxCPUs != maxCPUs { // max cpu slots is hard to change
@@ -177,11 +219,11 @@ func (obj *VirtRes) Init(init *engine.Init) error {
// event handlers so that we don't miss any events via race? // event handlers so that we don't miss any events via race?
xmlDesc, err := dom.GetXMLDesc(0) // 0 means no flags xmlDesc, err := dom.GetXMLDesc(0) // 0 means no flags
if err != nil { if err != nil {
return errwrap.Wrapf(err, "%s: Could not GetXMLDesc on init", obj) return errwrap.Wrapf(err, "could not GetXMLDesc on init")
} }
domXML := &libvirtxml.Domain{} domXML := &libvirtxml.Domain{}
if err := domXML.Unmarshal(xmlDesc); err != nil { if err := domXML.Unmarshal(xmlDesc); err != nil {
return errwrap.Wrapf(err, "%s: Could not unmarshal XML on init", obj) return errwrap.Wrapf(err, "could not unmarshal XML on init")
} }
// guest agent: domain->devices->channel->target->state == connected? // guest agent: domain->devices->channel->target->state == connected?
@@ -382,22 +424,22 @@ func (obj *VirtRes) Watch() error {
if state == libvirt.CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_CONNECTED { if state == libvirt.CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_CONNECTED {
obj.guestAgentConnected = true obj.guestAgentConnected = true
send = true send = true
obj.init.Logf("Guest agent connected") obj.init.Logf("guest agent connected")
} else if state == libvirt.CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_DISCONNECTED { } else if state == libvirt.CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_DISCONNECTED {
obj.guestAgentConnected = false obj.guestAgentConnected = false
// ignore CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_DOMAIN_STARTED // ignore CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_DOMAIN_STARTED
// events because they just tell you that guest agent channel was added // events because they just tell you that guest agent channel was added
if reason == libvirt.CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL { if reason == libvirt.CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL {
obj.init.Logf("Guest agent disconnected") obj.init.Logf("guest agent disconnected")
} }
} else { } else {
return fmt.Errorf("unknown %s guest agent state: %v", obj, state) return fmt.Errorf("unknown guest agent state: %v", state)
} }
case err := <-errorChan: case err := <-errorChan:
return fmt.Errorf("unknown %s libvirt error: %s", obj, err) return errwrap.Wrapf(err, "unknown libvirt error")
case <-obj.init.Done: // closed by the engine to signal shutdown case <-obj.init.Done: // closed by the engine to signal shutdown
return nil return nil
@@ -434,7 +476,7 @@ func (obj *VirtRes) domainCreate() (*libvirt.Domain, bool, error) {
if err != nil { if err != nil {
return dom, false, err // returned dom is invalid return dom, false, err // returned dom is invalid
} }
obj.init.Logf("Domain transient %s", state) obj.init.Logf("transient domain %s", state) // log the state
return dom, false, nil return dom, false, nil
} }
@@ -442,20 +484,20 @@ func (obj *VirtRes) domainCreate() (*libvirt.Domain, bool, error) {
if err != nil { if err != nil {
return dom, false, err // returned dom is invalid return dom, false, err // returned dom is invalid
} }
obj.init.Logf("Domain defined") obj.init.Logf("domain defined")
if obj.State == "running" { if obj.State == "running" {
if err := dom.Create(); err != nil { if err := dom.Create(); err != nil {
return dom, false, err return dom, false, err
} }
obj.init.Logf("Domain started") obj.init.Logf("domain started")
} }
if obj.State == "paused" { if obj.State == "paused" {
if err := dom.CreateWithFlags(libvirt.DOMAIN_START_PAUSED); err != nil { if err := dom.CreateWithFlags(libvirt.DOMAIN_START_PAUSED); err != nil {
return dom, false, err return dom, false, err
} }
obj.init.Logf("Domain created paused") obj.init.Logf("domain created paused")
} }
return dom, false, nil return dom, false, nil
@@ -483,7 +525,7 @@ func (obj *VirtRes) stateCheckApply(apply bool, dom *libvirt.Domain) (bool, erro
} }
if domInfo.State == libvirt.DOMAIN_BLOCKED { if domInfo.State == libvirt.DOMAIN_BLOCKED {
// TODO: what should happen? // TODO: what should happen?
return false, fmt.Errorf("domain %s is blocked", obj.Name()) return false, fmt.Errorf("domain is blocked")
} }
if !apply { if !apply {
return false, nil return false, nil
@@ -493,14 +535,14 @@ func (obj *VirtRes) stateCheckApply(apply bool, dom *libvirt.Domain) (bool, erro
return false, errwrap.Wrapf(err, "domain.Resume failed") return false, errwrap.Wrapf(err, "domain.Resume failed")
} }
checkOK = false checkOK = false
obj.init.Logf("Domain resumed") obj.init.Logf("domain resumed")
break break
} }
if err := dom.Create(); err != nil { if err := dom.Create(); err != nil {
return false, errwrap.Wrapf(err, "domain.Create failed") return false, errwrap.Wrapf(err, "domain.Create failed")
} }
checkOK = false checkOK = false
obj.init.Logf("Domain created") obj.init.Logf("domain created")
case "paused": case "paused":
if domInfo.State == libvirt.DOMAIN_PAUSED { if domInfo.State == libvirt.DOMAIN_PAUSED {
@@ -514,14 +556,14 @@ func (obj *VirtRes) stateCheckApply(apply bool, dom *libvirt.Domain) (bool, erro
return false, errwrap.Wrapf(err, "domain.Suspend failed") return false, errwrap.Wrapf(err, "domain.Suspend failed")
} }
checkOK = false checkOK = false
obj.init.Logf("Domain paused") obj.init.Logf("domain paused")
break break
} }
if err := dom.CreateWithFlags(libvirt.DOMAIN_START_PAUSED); err != nil { if err := dom.CreateWithFlags(libvirt.DOMAIN_START_PAUSED); err != nil {
return false, errwrap.Wrapf(err, "domain.CreateWithFlags failed") return false, errwrap.Wrapf(err, "domain.CreateWithFlags failed")
} }
checkOK = false checkOK = false
obj.init.Logf("Domain created paused") obj.init.Logf("domain created paused")
case "shutoff": case "shutoff":
if domInfo.State == libvirt.DOMAIN_SHUTOFF || domInfo.State == libvirt.DOMAIN_SHUTDOWN { if domInfo.State == libvirt.DOMAIN_SHUTOFF || domInfo.State == libvirt.DOMAIN_SHUTDOWN {
@@ -535,7 +577,7 @@ func (obj *VirtRes) stateCheckApply(apply bool, dom *libvirt.Domain) (bool, erro
return false, errwrap.Wrapf(err, "domain.Destroy failed") return false, errwrap.Wrapf(err, "domain.Destroy failed")
} }
checkOK = false checkOK = false
obj.init.Logf("Domain destroyed") obj.init.Logf("domain destroyed")
} }
return checkOK, nil return checkOK, nil
@@ -561,7 +603,7 @@ func (obj *VirtRes) attrCheckApply(apply bool, dom *libvirt.Domain) (bool, error
if err := dom.SetMemory(obj.Memory); err != nil { if err := dom.SetMemory(obj.Memory); err != nil {
return false, errwrap.Wrapf(err, "domain.SetMemory failed") return false, errwrap.Wrapf(err, "domain.SetMemory failed")
} }
obj.init.Logf("Memory changed to %d", obj.Memory) obj.init.Logf("memory changed to: %d", obj.Memory)
} }
// check cpus // check cpus
@@ -600,7 +642,7 @@ func (obj *VirtRes) attrCheckApply(apply bool, dom *libvirt.Domain) (bool, error
return false, errwrap.Wrapf(err, "domain.SetVcpus failed") return false, errwrap.Wrapf(err, "domain.SetVcpus failed")
} }
checkOK = false checkOK = false
obj.init.Logf("CPUs (hot) changed to %d", obj.CPUs) obj.init.Logf("cpus (hot) changed to: %d", obj.CPUs)
case libvirt.DOMAIN_SHUTOFF, libvirt.DOMAIN_SHUTDOWN: case libvirt.DOMAIN_SHUTOFF, libvirt.DOMAIN_SHUTDOWN:
if !obj.Transient { if !obj.Transient {
@@ -612,7 +654,7 @@ func (obj *VirtRes) attrCheckApply(apply bool, dom *libvirt.Domain) (bool, error
return false, errwrap.Wrapf(err, "domain.SetVcpus failed") return false, errwrap.Wrapf(err, "domain.SetVcpus failed")
} }
checkOK = false checkOK = false
obj.init.Logf("CPUs (cold) changed to %d", obj.CPUs) obj.init.Logf("cpus (cold) changed to: %d", obj.CPUs)
} }
default: default:
@@ -643,7 +685,7 @@ func (obj *VirtRes) attrCheckApply(apply bool, dom *libvirt.Domain) (bool, error
return false, errwrap.Wrapf(err, "domain.SetVcpus failed") return false, errwrap.Wrapf(err, "domain.SetVcpus failed")
} }
checkOK = false checkOK = false
obj.init.Logf("CPUs (guest) changed to %d", obj.CPUs) obj.init.Logf("cpus (guest) changed to: %d", obj.CPUs)
} }
} }
@@ -667,7 +709,7 @@ func (obj *VirtRes) domainShutdownSync(apply bool, dom *libvirt.Domain) (bool, e
return false, errwrap.Wrapf(err, "domain.GetInfo failed") return false, errwrap.Wrapf(err, "domain.GetInfo failed")
} }
if domInfo.State == libvirt.DOMAIN_SHUTOFF || domInfo.State == libvirt.DOMAIN_SHUTDOWN { if domInfo.State == libvirt.DOMAIN_SHUTOFF || domInfo.State == libvirt.DOMAIN_SHUTDOWN {
obj.init.Logf("Shutdown") obj.init.Logf("shutdown")
break break
} }
@@ -679,7 +721,7 @@ func (obj *VirtRes) domainShutdownSync(apply bool, dom *libvirt.Domain) (bool, e
obj.processExitChan = make(chan struct{}) obj.processExitChan = make(chan struct{})
// if machine shuts down before we call this, we error; // if machine shuts down before we call this, we error;
// this isn't ideal, but it happened due to user error! // this isn't ideal, but it happened due to user error!
obj.init.Logf("Running shutdown") obj.init.Logf("running shutdown")
if err := dom.Shutdown(); err != nil { if err := dom.Shutdown(); err != nil {
// FIXME: if machine is already shutdown completely, return early // FIXME: if machine is already shutdown completely, return early
return false, errwrap.Wrapf(err, "domain.Shutdown failed") return false, errwrap.Wrapf(err, "domain.Shutdown failed")
@@ -700,7 +742,7 @@ func (obj *VirtRes) domainShutdownSync(apply bool, dom *libvirt.Domain) (bool, e
// https://libvirt.org/formatdomain.html#elementsEvents // https://libvirt.org/formatdomain.html#elementsEvents
continue continue
case <-timeout: case <-timeout:
return false, fmt.Errorf("%s: didn't shutdown after %d seconds", obj, MaxShutdownDelayTimeout) return false, fmt.Errorf("didn't shutdown after %d seconds", MaxShutdownDelayTimeout)
} }
} }
@@ -710,8 +752,8 @@ func (obj *VirtRes) domainShutdownSync(apply bool, dom *libvirt.Domain) (bool, e
// CheckApply checks the resource state and applies the resource if the bool // CheckApply checks the resource state and applies the resource if the bool
// input is true. It returns error info and if the state check passed or not. // input is true. It returns error info and if the state check passed or not.
func (obj *VirtRes) CheckApply(apply bool) (bool, error) { func (obj *VirtRes) CheckApply(apply bool) (bool, error) {
if obj.conn == nil { if obj.conn == nil { // programming error?
panic("virt: CheckApply is being called with nil connection") return false, fmt.Errorf("got called with nil connection")
} }
// if we do the restart, we must flip the flag back to false as evidence // if we do the restart, we must flip the flag back to false as evidence
var restart bool // do we need to do a restart? var restart bool // do we need to do a restart?
@@ -772,7 +814,7 @@ func (obj *VirtRes) CheckApply(apply bool) (bool, error) {
if err := dom.Undefine(); err != nil { if err := dom.Undefine(); err != nil {
return false, errwrap.Wrapf(err, "domain.Undefine failed") return false, errwrap.Wrapf(err, "domain.Undefine failed")
} }
obj.init.Logf("Domain undefined") obj.init.Logf("domain undefined")
} else { } else {
domXML, err := dom.GetXMLDesc(libvirt.DOMAIN_XML_INACTIVE) domXML, err := dom.GetXMLDesc(libvirt.DOMAIN_XML_INACTIVE)
if err != nil { if err != nil {
@@ -781,7 +823,7 @@ func (obj *VirtRes) CheckApply(apply bool) (bool, error) {
if _, err = obj.conn.DomainDefineXML(domXML); err != nil { if _, err = obj.conn.DomainDefineXML(domXML); err != nil {
return false, errwrap.Wrapf(err, "conn.DomainDefineXML failed") return false, errwrap.Wrapf(err, "conn.DomainDefineXML failed")
} }
obj.init.Logf("Domain defined") obj.init.Logf("domain defined")
} }
checkOK = false checkOK = false
} }
@@ -829,7 +871,7 @@ func (obj *VirtRes) CheckApply(apply bool) (bool, error) {
// we had to do a restart, we didn't, and we should error if it was needed // we had to do a restart, we didn't, and we should error if it was needed
if obj.restartScheduled && restart == true && obj.RestartOnDiverge == "error" { if obj.restartScheduled && restart == true && obj.RestartOnDiverge == "error" {
return false, fmt.Errorf("%s: needed restart but didn't! (RestartOnDiverge: %v)", obj, obj.RestartOnDiverge) return false, fmt.Errorf("needed restart but didn't! (RestartOnDiverge: %s)", obj.RestartOnDiverge)
} }
return checkOK, nil // w00t return checkOK, nil // w00t
@@ -959,27 +1001,6 @@ type DiskDevice struct {
Type string `lang:"type" yaml:"type"` Type string `lang:"type" yaml:"type"`
} }
// CDRomDevice represents a CDRom device that is attached to the virt machine.
type CDRomDevice struct {
Source string `lang:"source" yaml:"source"`
Type string `lang:"type" yaml:"type"`
}
// NetworkDevice represents a network card that is attached to the virt machine.
type NetworkDevice struct {
Name string `lang:"name" yaml:"name"`
MAC string `lang:"mac" yaml:"mac"`
}
// FilesystemDevice represents a filesystem that is attached to the virt
// machine.
type FilesystemDevice struct {
Access string `lang:"access" yaml:"access"`
Source string `lang:"source" yaml:"source"`
Target string `lang:"target" yaml:"target"`
ReadOnly bool `lang:"read_only" yaml:"read_only"`
}
// GetXML returns the XML representation of this device. // GetXML returns the XML representation of this device.
func (obj *DiskDevice) GetXML(idx int) string { func (obj *DiskDevice) GetXML(idx int) string {
source, _ := util.ExpandHome(obj.Source) // TODO: should we handle errors? source, _ := util.ExpandHome(obj.Source) // TODO: should we handle errors?
@@ -992,6 +1013,32 @@ func (obj *DiskDevice) GetXML(idx int) string {
return b return b
} }
// Cmp compares two DiskDevice's and returns an error if they are not
// equivalent.
func (obj *DiskDevice) Cmp(dev *DiskDevice) error {
if (obj == nil) != (dev == nil) { // xor
return fmt.Errorf("the DiskDevice differs")
}
if obj == nil && dev == nil {
return nil
}
if obj.Source != dev.Source {
return fmt.Errorf("the Source differs")
}
if obj.Type != dev.Type {
return fmt.Errorf("the Type differs")
}
return nil
}
// CDRomDevice represents a CDRom device that is attached to the virt machine.
type CDRomDevice struct {
Source string `lang:"source" yaml:"source"`
Type string `lang:"type" yaml:"type"`
}
// GetXML returns the XML representation of this device. // GetXML returns the XML representation of this device.
func (obj *CDRomDevice) GetXML(idx int) string { func (obj *CDRomDevice) GetXML(idx int) string {
source, _ := util.ExpandHome(obj.Source) // TODO: should we handle errors? source, _ := util.ExpandHome(obj.Source) // TODO: should we handle errors?
@@ -1005,6 +1052,32 @@ func (obj *CDRomDevice) GetXML(idx int) string {
return b return b
} }
// Cmp compares two CDRomDevice's and returns an error if they are not
// equivalent.
func (obj *CDRomDevice) Cmp(dev *CDRomDevice) error {
if (obj == nil) != (dev == nil) { // xor
return fmt.Errorf("the CDRomDevice differs")
}
if obj == nil && dev == nil {
return nil
}
if obj.Source != dev.Source {
return fmt.Errorf("the Source differs")
}
if obj.Type != dev.Type {
return fmt.Errorf("the Type differs")
}
return nil
}
// NetworkDevice represents a network card that is attached to the virt machine.
type NetworkDevice struct {
Name string `lang:"name" yaml:"name"`
MAC string `lang:"mac" yaml:"mac"`
}
// GetXML returns the XML representation of this device. // GetXML returns the XML representation of this device.
func (obj *NetworkDevice) GetXML(idx int) string { func (obj *NetworkDevice) GetXML(idx int) string {
if obj.MAC == "" { if obj.MAC == "" {
@@ -1018,6 +1091,35 @@ func (obj *NetworkDevice) GetXML(idx int) string {
return b return b
} }
// Cmp compares two NetworkDevice's and returns an error if they are not
// equivalent.
func (obj *NetworkDevice) Cmp(dev *NetworkDevice) error {
if (obj == nil) != (dev == nil) { // xor
return fmt.Errorf("the NetworkDevice differs")
}
if obj == nil && dev == nil {
return nil
}
if obj.Name != dev.Name {
return fmt.Errorf("the Name differs")
}
if obj.MAC != dev.MAC {
return fmt.Errorf("the MAC differs")
}
return nil
}
// FilesystemDevice represents a filesystem that is attached to the virt
// machine.
type FilesystemDevice struct {
Access string `lang:"access" yaml:"access"`
Source string `lang:"source" yaml:"source"`
Target string `lang:"target" yaml:"target"`
ReadOnly bool `lang:"read_only" yaml:"read_only"`
}
// GetXML returns the XML representation of this device. // GetXML returns the XML representation of this device.
func (obj *FilesystemDevice) GetXML(idx int) string { func (obj *FilesystemDevice) GetXML(idx int) string {
source, _ := util.ExpandHome(obj.Source) // TODO: should we handle errors? source, _ := util.ExpandHome(obj.Source) // TODO: should we handle errors?
@@ -1036,39 +1138,61 @@ func (obj *FilesystemDevice) GetXML(idx int) string {
return b return b
} }
// Cmp compares two resources and returns an error if they are not equivalent. // Cmp compares two FilesystemDevice's and returns an error if they are not
func (obj *VirtRes) Cmp(r engine.Res) error { // equivalent.
if !obj.Compare(r) { func (obj *FilesystemDevice) Cmp(dev *FilesystemDevice) error {
return fmt.Errorf("did not compare") if (obj == nil) != (dev == nil) { // xor
return fmt.Errorf("the FilesystemDevice differs")
} }
if obj == nil && dev == nil {
return nil
}
if obj.Access != dev.Access {
return fmt.Errorf("the Access differs")
}
if obj.Source != dev.Source {
return fmt.Errorf("the Source differs")
}
if obj.Target != dev.Target {
return fmt.Errorf("the Target differs")
}
if obj.ReadOnly != dev.ReadOnly {
return fmt.Errorf("the ReadOnly differs")
}
return nil return nil
} }
// Compare two resources and return if they are equivalent. // Cmp compares two resources and returns an error if they are not equivalent.
func (obj *VirtRes) Compare(r engine.Res) bool { func (obj *VirtRes) Cmp(r engine.Res) error {
// we can only compare VirtRes to others of the same resource kind // we can only compare VirtRes to others of the same resource kind
res, ok := r.(*VirtRes) res, ok := r.(*VirtRes)
if !ok { if !ok {
return false return fmt.Errorf("not a %s", obj.Kind())
} }
if obj.URI != res.URI { if obj.URI != res.URI {
return false return fmt.Errorf("the URI differs")
} }
if obj.State != res.State { if obj.State != res.State {
return false return fmt.Errorf("the State differs")
} }
if obj.Transient != res.Transient { if obj.Transient != res.Transient {
return false return fmt.Errorf("the Transient differs")
} }
if obj.CPUs != res.CPUs { if obj.CPUs != res.CPUs {
return false return fmt.Errorf("the CPUs differ")
} }
// we can't change this property while machine is running! // we can't change this property while machine is running!
// we do need to return false, so that a new struct gets built, // we do need to return false, so that a new struct gets built,
// which will cause at least one Init() & CheckApply() to run. // which will cause at least one Init() & CheckApply() to run.
if obj.MaxCPUs != res.MaxCPUs { if obj.MaxCPUs != res.MaxCPUs {
return false return fmt.Errorf("the MaxCPUs differ")
}
if obj.HotCPUs != res.HotCPUs {
return fmt.Errorf("the HotCPUs differ")
} }
// TODO: can we skip the compare of certain properties such as // TODO: can we skip the compare of certain properties such as
// Memory because this object (but with different memory) can be // Memory because this object (but with different memory) can be
@@ -1076,26 +1200,61 @@ func (obj *VirtRes) Compare(r engine.Res) bool {
// We would need to run some sort of "old struct update", to get // We would need to run some sort of "old struct update", to get
// the new values, but that's easy to add. // the new values, but that's easy to add.
if obj.Memory != res.Memory { if obj.Memory != res.Memory {
return false return fmt.Errorf("the Memory differs")
} }
// TODO:
//if obj.Boot != res.Boot {
// return false
//}
//if obj.Disk != res.Disk {
// return false
//}
//if obj.CDRom != res.CDRom {
// return false
//}
//if obj.Network != res.Network {
// return false
//}
//if obj.Filesystem != res.Filesystem {
// return false
//}
return true if obj.OSInit != res.OSInit {
return fmt.Errorf("the OSInit differs")
}
if err := engineUtil.StrListCmp(obj.Boot, res.Boot); err != nil {
return errwrap.Wrapf(err, "the Boot differs")
}
if len(obj.Disk) != len(res.Disk) {
return fmt.Errorf("the Disk length differs")
}
for i := range obj.Disk {
if err := obj.Disk[i].Cmp(res.Disk[i]); err != nil {
return errwrap.Wrapf(err, "the Disk differs")
}
}
if len(obj.CDRom) != len(res.CDRom) {
return fmt.Errorf("the CDRom length differs")
}
for i := range obj.CDRom {
if err := obj.CDRom[i].Cmp(res.CDRom[i]); err != nil {
return errwrap.Wrapf(err, "the CDRom differs")
}
}
if len(obj.Network) != len(res.Network) {
return fmt.Errorf("the Network length differs")
}
for i := range obj.Network {
if err := obj.Network[i].Cmp(res.Network[i]); err != nil {
return errwrap.Wrapf(err, "the Network differs")
}
}
if len(obj.Filesystem) != len(res.Filesystem) {
return fmt.Errorf("the Filesystem length differs")
}
for i := range obj.Filesystem {
if err := obj.Filesystem[i].Cmp(res.Filesystem[i]); err != nil {
return errwrap.Wrapf(err, "the Filesystem differs")
}
}
if err := obj.Auth.Cmp(res.Auth); err != nil {
return errwrap.Wrapf(err, "the Auth differs")
}
if obj.RestartOnDiverge != res.RestartOnDiverge {
return fmt.Errorf("the RestartOnDiverge differs")
}
if obj.RestartOnRefresh != res.RestartOnRefresh {
return fmt.Errorf("the RestartOnRefresh differs")
}
return nil
} }
// VirtUID is the UID struct for FileRes. // VirtUID is the UID struct for FileRes.

37
engine/util/cmp.go Normal file
View File

@@ -0,0 +1,37 @@
// Mgmt
// Copyright (C) 2013-2018+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package util
import (
"fmt"
)
// StrListCmp compares two lists of strings. If they are not the same length or
// do not contain identical strings in the same order, then this errors.
func StrListCmp(x, y []string) error {
if len(x) != len(y) {
return fmt.Errorf("length differs")
}
for i := range x {
if x[i] != y[i] {
return fmt.Errorf("the elements at position %d differed", i)
}
}
return nil
}