engine: resources: Clean up virt code

There was and still is a bunch of terrible mess in this code. This does
some initial cleanup, and also fixes an important bug!

If you're provisioning a vmhost from scratch, then the function engine
might do some work to get the libvirt related services running before
the virt resource is used to build a vm. Since we had connection code in
Init() it would fail if it wasn't up already, meaning we'd have to write
fancy mcl code to avoid this, or we could do this refactor and keep
things more logical.
This commit is contained in:
James Shubin
2025-09-13 22:40:28 -04:00
parent d59ae2e007
commit 1b39a780e1
5 changed files with 376 additions and 216 deletions

View File

@@ -0,0 +1,175 @@
// Mgmt
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
//go:build !novirt
package resources
import (
"fmt"
"math/rand"
"sync"
"time"
"github.com/purpleidea/mgmt/util/errwrap"
libvirt "libvirt.org/go/libvirt" // gitlab.com/libvirt/libvirt-go-module
)
var (
// shared by all virt resources
libvirtInitialized = false
libvirtMutex *sync.Mutex
)
func init() {
libvirtMutex = &sync.Mutex{}
}
type virtURISchemeType int
const (
defaultURI virtURISchemeType = iota
lxcURI
)
// libvirtInit is called in the Init method of any virt resource. It must be run
// before any connection to the hypervisor is made!
func libvirtInit() error {
libvirtMutex.Lock()
defer libvirtMutex.Unlock()
if libvirtInitialized {
return nil // done early
}
if err := libvirt.EventRegisterDefaultImpl(); err != nil {
return errwrap.Wrapf(err, "method EventRegisterDefaultImpl failed")
}
libvirtInitialized = true
return nil
}
// randMAC returns a random mac address in the libvirt range.
func randMAC() string {
rand.Seed(time.Now().UnixNano())
return "52:54:00" +
fmt.Sprintf(":%x", rand.Intn(255)) +
fmt.Sprintf(":%x", rand.Intn(255)) +
fmt.Sprintf(":%x", rand.Intn(255))
}
// isNotFound tells us if this is a domain or network not found error.
// TODO: expand this with other ERR_NO_? values eventually.
func isNotFound(err error) bool {
if err == nil {
return false
}
virErr, ok := err.(libvirt.Error)
if !ok {
return false
}
if virErr.Code == libvirt.ERR_NO_DOMAIN {
// domain not found
return true
}
if virErr.Code == libvirt.ERR_NO_NETWORK {
// network not found
return true
}
return false // some other error
}
// VirtAuth is used to pass credentials to libvirt.
type VirtAuth struct {
Username string `lang:"username" yaml:"username"`
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
}
// Connect is the connect helper for the libvirt connection. It can handle auth.
func (obj *VirtAuth) Connect(uri string) (conn *libvirt.Connect, version uint32, err error) {
if obj != nil {
callback := func(creds []*libvirt.ConnectCredential) {
// Populate credential structs with the
// prepared username/password values
for _, cred := range creds {
if cred.Type == libvirt.CRED_AUTHNAME {
cred.Result = obj.Username
cred.ResultLen = len(cred.Result)
} else if cred.Type == libvirt.CRED_PASSPHRASE {
cred.Result = obj.Password
cred.ResultLen = len(cred.Result)
}
}
}
auth := &libvirt.ConnectAuth{
CredType: []libvirt.ConnectCredentialType{
libvirt.CRED_AUTHNAME, libvirt.CRED_PASSPHRASE,
},
Callback: callback,
}
conn, err = libvirt.NewConnectWithAuth(uri, auth, 0)
if err == nil {
if v, err := conn.GetLibVersion(); err == nil {
version = v
}
}
}
if obj == nil || err != nil {
conn, err = libvirt.NewConnect(uri)
if err == nil {
if v, err := conn.GetLibVersion(); err == nil {
version = v
}
}
}
return
}