mirror of
https://github.com/AlchemillaHQ/Sylve.git
synced 2026-06-26 02:45:10 +03:00
732 lines
18 KiB
Go
732 lines
18 KiB
Go
// SPDX-License-Identifier: BSD-2-Clause
|
|
//
|
|
// Copyright (c) 2025 The FreeBSD Foundation.
|
|
//
|
|
// This software was developed by Hayzam Sherif <hayzam@alchemilla.io>
|
|
// of Alchemilla Ventures Pvt. Ltd. <hello@alchemilla.io>,
|
|
// under sponsorship from the FreeBSD Foundation.
|
|
|
|
package libvirt
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
networkModels "github.com/alchemillahq/sylve/internal/db/models/network"
|
|
vmModels "github.com/alchemillahq/sylve/internal/db/models/vm"
|
|
libvirtServiceInterfaces "github.com/alchemillahq/sylve/internal/interfaces/services/libvirt"
|
|
"github.com/alchemillahq/sylve/internal/logger"
|
|
"github.com/alchemillahq/sylve/pkg/utils"
|
|
|
|
"github.com/beevik/etree"
|
|
)
|
|
|
|
func (s *Service) NetworkDetach(rid uint, networkId uint) error {
|
|
if err := s.requireVMMutationOwnership(rid); err != nil {
|
|
return err
|
|
}
|
|
if err := s.requireConnection(); err != nil {
|
|
return err
|
|
}
|
|
|
|
inactive, err := s.IsDomainInactive(rid)
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_check_vm_inactive: %w", err)
|
|
}
|
|
|
|
if !inactive {
|
|
return fmt.Errorf("vm_is_active: cannot_detach_network")
|
|
}
|
|
|
|
xmlDesc, err := s.GetVMXML(rid)
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_get_vm_xml: %w", err)
|
|
}
|
|
|
|
var network vmModels.Network
|
|
if err := s.DB.
|
|
Preload("AddressObj").
|
|
Preload("AddressObj.Entries").
|
|
First(&network, "id = ?", networkId).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_network: %w", err)
|
|
}
|
|
|
|
if err := network.AfterFind(s.DB); err != nil {
|
|
return err
|
|
}
|
|
|
|
var mac string
|
|
|
|
if network.AddressObj == nil || len(network.AddressObj.Entries) == 0 {
|
|
logger.L.Debug().Msgf("Network detach: network_mac_address_missing for network ID %d, proceeding with detach", networkId)
|
|
} else {
|
|
mac = strings.TrimSpace(strings.ToLower(network.AddressObj.Entries[0].Value))
|
|
}
|
|
|
|
if len(mac) != 0 {
|
|
doc := etree.NewDocument()
|
|
if err := doc.ReadFromString(xmlDesc); err != nil {
|
|
return fmt.Errorf("failed_to_parse_vm_xml: %w", err)
|
|
}
|
|
|
|
found := false
|
|
for _, iface := range doc.FindElements("//interface[@type='bridge']") {
|
|
macEl := iface.FindElement("mac")
|
|
if macEl == nil {
|
|
continue
|
|
}
|
|
|
|
addrAttr := macEl.SelectAttr("address")
|
|
if addrAttr == nil {
|
|
continue
|
|
}
|
|
|
|
if strings.EqualFold(strings.TrimSpace(addrAttr.Value), mac) {
|
|
iface.Parent().RemoveChild(iface)
|
|
found = true
|
|
logger.L.Debug().Msgf("Removed interface with MAC: %s", addrAttr.Value)
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
logger.L.Debug().Msgf("Network detach: network_interface_not_found_in_xml: %s", mac)
|
|
if err := s.DB.Delete(&network).Error; err != nil {
|
|
return fmt.Errorf("failed_to_delete_network_record: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
newXML, err := doc.WriteToString()
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_serialize_modified_xml: %w", err)
|
|
}
|
|
|
|
domain, err := s.conn().DomainLookupByName(strconv.Itoa(int(rid)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_lookup_domain_by_name: %w", err)
|
|
}
|
|
|
|
if err := s.conn().DomainUndefineFlags(domain, 0); err != nil {
|
|
return fmt.Errorf("failed_to_undefine_domain: %w", err)
|
|
}
|
|
|
|
if _, err := s.conn().DomainDefineXML(newXML); err != nil {
|
|
return fmt.Errorf("failed_to_define_domain_with_modified_xml: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := s.DB.Delete(&network).Error; err != nil {
|
|
return fmt.Errorf("failed_to_delete_network_record: %w", err)
|
|
}
|
|
|
|
err = s.WriteVMJson(rid)
|
|
if err != nil {
|
|
logger.L.Error().Err(err).Msg("Failed to write VM JSON after network detach")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) NetworkAttach(req libvirtServiceInterfaces.NetworkAttachRequest) error {
|
|
if err := s.requireVMMutationOwnership(req.RID); err != nil {
|
|
return err
|
|
}
|
|
if err := s.requireConnection(); err != nil {
|
|
return err
|
|
}
|
|
|
|
inactive, err := s.IsDomainInactive(req.RID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_check_vm_inactive: %w", err)
|
|
}
|
|
|
|
if !inactive {
|
|
return fmt.Errorf("vm_is_active: cannot_attach_network")
|
|
}
|
|
|
|
if req.Emulation == "" || (req.Emulation != "virtio" && req.Emulation != "e1000") {
|
|
return fmt.Errorf("invalid_emulation_type: %s", req.Emulation)
|
|
}
|
|
|
|
macObjId := uint(0)
|
|
if req.MacId != nil {
|
|
macObjId = *req.MacId
|
|
}
|
|
|
|
swType := ""
|
|
|
|
var stdSwitch networkModels.StandardSwitch
|
|
if err := s.DB.First(&stdSwitch, "name = ?", req.SwitchName).Error; err == nil {
|
|
swType = "standard"
|
|
}
|
|
|
|
var manualSwitch networkModels.ManualSwitch
|
|
if err := s.DB.First(&manualSwitch, "name = ?", req.SwitchName).Error; err == nil {
|
|
swType = "manual"
|
|
}
|
|
|
|
if swType == "" {
|
|
return fmt.Errorf("switch_not_found: %s", req.SwitchName)
|
|
}
|
|
|
|
vms, err := s.ListVMs()
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_list_vms: %w", err)
|
|
}
|
|
|
|
var vm *vmModels.VM
|
|
for _, v := range vms {
|
|
if v.RID == req.RID {
|
|
vm = &v
|
|
break
|
|
}
|
|
}
|
|
|
|
var sw any
|
|
|
|
switch swType {
|
|
case "standard":
|
|
sw = stdSwitch
|
|
case "manual":
|
|
sw = manualSwitch
|
|
default:
|
|
return fmt.Errorf("unknown_switch_type: %s", swType)
|
|
}
|
|
|
|
if macObjId == 0 {
|
|
macAddress := utils.GenerateRandomMAC()
|
|
|
|
var base string
|
|
|
|
switch v := sw.(type) {
|
|
case networkModels.StandardSwitch:
|
|
base = fmt.Sprintf("%s-%s", vm.Name, v.Name)
|
|
case networkModels.ManualSwitch:
|
|
base = fmt.Sprintf("%s-%s", vm.Name, v.Name)
|
|
default:
|
|
return fmt.Errorf("invalid switch type %T", v)
|
|
}
|
|
|
|
name := base
|
|
|
|
for i := 0; ; i++ {
|
|
if i > 0 {
|
|
name = fmt.Sprintf("%s-%d", base, i)
|
|
}
|
|
|
|
var exists int64
|
|
|
|
if err := s.DB.
|
|
Model(&networkModels.Object{}).
|
|
Where("name = ?", name).
|
|
Limit(1).
|
|
Count(&exists).Error; err != nil {
|
|
return fmt.Errorf("failed_to_check_mac_object_exists: %w", err)
|
|
}
|
|
|
|
if exists == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
macObj := networkModels.Object{
|
|
Name: name,
|
|
Type: "Mac",
|
|
}
|
|
|
|
if err := s.DB.Create(&macObj).Error; err != nil {
|
|
return fmt.Errorf("failed_to_create_mac_object: %w", err)
|
|
}
|
|
|
|
macEntry := networkModels.ObjectEntry{
|
|
ObjectID: macObj.ID,
|
|
Value: macAddress,
|
|
}
|
|
|
|
if err := s.DB.Create(&macEntry).Error; err != nil {
|
|
return fmt.Errorf("failed_to_create_mac_entry: %w", err)
|
|
}
|
|
|
|
macObjId = macObj.ID
|
|
} else {
|
|
var macObj networkModels.Object
|
|
if err := s.DB.Preload("Entries").First(&macObj, macObjId).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_mac_object: %w", err)
|
|
}
|
|
|
|
if macObj.Type != "Mac" {
|
|
return fmt.Errorf("invalid_mac_object_type: %s", macObj.Type)
|
|
}
|
|
|
|
if len(macObj.Entries) == 0 {
|
|
return fmt.Errorf("mac_object_has_no_entries: %d", macObjId)
|
|
}
|
|
|
|
var otherNetworks []vmModels.Network
|
|
if err := s.DB.Where("mac_id = ? AND vm_id != ?", macObjId, vm.ID).
|
|
Find(&otherNetworks).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_other_networks_using_mac_object: %w", err)
|
|
}
|
|
}
|
|
|
|
var switchId uint
|
|
|
|
switch v := sw.(type) {
|
|
case networkModels.StandardSwitch:
|
|
switchId = v.ID
|
|
case networkModels.ManualSwitch:
|
|
switchId = v.ID
|
|
default:
|
|
return fmt.Errorf("invalid switch type %T", v)
|
|
}
|
|
|
|
network := vmModels.Network{
|
|
VMID: vm.ID,
|
|
SwitchID: switchId,
|
|
SwitchType: swType,
|
|
MacID: &macObjId,
|
|
Emulation: req.Emulation,
|
|
}
|
|
|
|
if err := s.DB.Create(&network).Error; err != nil {
|
|
return fmt.Errorf("failed_to_create_network_record: %w", err)
|
|
}
|
|
|
|
var macAddress string
|
|
|
|
if macObjId != 0 {
|
|
var macObj networkModels.Object
|
|
if err := s.DB.Preload("Entries").First(&macObj, macObjId).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_mac_object: %w", err)
|
|
}
|
|
if len(macObj.Entries) == 0 {
|
|
return fmt.Errorf("mac_object_has_no_entries: %d", macObjId)
|
|
}
|
|
macAddress = macObj.Entries[0].Value
|
|
}
|
|
|
|
xmlDesc, err := s.GetVMXML(req.RID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_get_vm_xml: %w", err)
|
|
}
|
|
|
|
doc := etree.NewDocument()
|
|
if err := doc.ReadFromString(xmlDesc); err != nil {
|
|
return fmt.Errorf("failed_to_parse_vm_xml: %w", err)
|
|
}
|
|
|
|
domainEl := doc.SelectElement("domain")
|
|
if domainEl == nil {
|
|
return fmt.Errorf("malformed_vm_xml: missing <domain> element")
|
|
}
|
|
|
|
devicesEl := domainEl.FindElement("devices")
|
|
if devicesEl == nil {
|
|
devicesEl = etree.NewElement("devices")
|
|
domainEl.AddChild(devicesEl)
|
|
}
|
|
|
|
ifaceEl := etree.NewElement("interface")
|
|
ifaceEl.CreateAttr("type", "bridge")
|
|
|
|
macEl := etree.NewElement("mac")
|
|
macEl.CreateAttr("address", macAddress)
|
|
ifaceEl.AddChild(macEl)
|
|
|
|
sourceEl := etree.NewElement("source")
|
|
|
|
switch swType {
|
|
case "manual":
|
|
manualSwitch := sw.(networkModels.ManualSwitch)
|
|
sourceEl.CreateAttr("bridge", manualSwitch.Bridge)
|
|
case "standard":
|
|
stdSwitch := sw.(networkModels.StandardSwitch)
|
|
sourceEl.CreateAttr("bridge", stdSwitch.BridgeName)
|
|
}
|
|
|
|
ifaceEl.AddChild(sourceEl)
|
|
|
|
modelEl := etree.NewElement("model")
|
|
modelEl.CreateAttr("type", network.Emulation)
|
|
ifaceEl.AddChild(modelEl)
|
|
|
|
devicesEl.AddChild(ifaceEl)
|
|
|
|
newXML, err := doc.WriteToString()
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_serialize_modified_xml: %w", err)
|
|
}
|
|
|
|
domain, err := s.conn().DomainLookupByName(strconv.Itoa(int(req.RID)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_lookup_domain_by_name: %w", err)
|
|
}
|
|
|
|
if err := s.conn().DomainUndefineFlags(domain, 0); err != nil {
|
|
return fmt.Errorf("failed_to_undefine_domain: %w", err)
|
|
}
|
|
|
|
if _, err := s.conn().DomainDefineXML(newXML); err != nil {
|
|
return fmt.Errorf("failed_to_define_domain_with_modified_xml: %w", err)
|
|
}
|
|
|
|
err = s.WriteVMJson(vm.RID)
|
|
if err != nil {
|
|
logger.L.Error().Err(err).Msg("Failed to write VM JSON after network attach")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) NetworkUpdate(req libvirtServiceInterfaces.NetworkUpdateRequest) error {
|
|
var network vmModels.Network
|
|
if err := s.DB.
|
|
Preload("AddressObj").
|
|
Preload("AddressObj.Entries").
|
|
First(&network, "id = ?", req.NetworkID).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_network: %w", err)
|
|
}
|
|
|
|
var vm vmModels.VM
|
|
if err := s.DB.First(&vm, "id = ?", network.VMID).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_vm: %w", err)
|
|
}
|
|
if err := s.requireVMMutationOwnership(vm.RID); err != nil {
|
|
return err
|
|
}
|
|
if err := s.requireConnection(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if req.Emulation == "" || (req.Emulation != "virtio" && req.Emulation != "e1000") {
|
|
return fmt.Errorf("invalid_emulation_type: %s", req.Emulation)
|
|
}
|
|
|
|
inactive, err := s.IsDomainInactive(vm.RID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_check_vm_inactive: %w", err)
|
|
}
|
|
|
|
if !inactive {
|
|
return fmt.Errorf("vm_is_active: cannot_update_network")
|
|
}
|
|
|
|
oldMac := ""
|
|
if network.AddressObj != nil && len(network.AddressObj.Entries) > 0 {
|
|
oldMac = strings.TrimSpace(strings.ToLower(network.AddressObj.Entries[0].Value))
|
|
}
|
|
|
|
if oldMac == "" {
|
|
return fmt.Errorf("network_mac_address_missing")
|
|
}
|
|
|
|
switchID := uint(0)
|
|
switchType := ""
|
|
bridgeName := ""
|
|
dbSwName := ""
|
|
|
|
var stdSwitch networkModels.StandardSwitch
|
|
if err := s.DB.First(&stdSwitch, "name = ?", req.SwitchName).Error; err == nil {
|
|
switchID = stdSwitch.ID
|
|
switchType = "standard"
|
|
bridgeName = stdSwitch.BridgeName
|
|
dbSwName = stdSwitch.Name
|
|
} else {
|
|
var manualSwitch networkModels.ManualSwitch
|
|
if err := s.DB.First(&manualSwitch, "name = ?", req.SwitchName).Error; err == nil {
|
|
switchID = manualSwitch.ID
|
|
switchType = "manual"
|
|
bridgeName = manualSwitch.Bridge
|
|
dbSwName = manualSwitch.Name
|
|
}
|
|
}
|
|
|
|
if switchType == "" || switchID == 0 {
|
|
return fmt.Errorf("switch_not_found: %s", req.SwitchName)
|
|
}
|
|
|
|
var macObjId uint
|
|
if network.MacID != nil {
|
|
macObjId = *network.MacID
|
|
}
|
|
|
|
forceNewMac := false
|
|
if req.MacId != nil {
|
|
if *req.MacId == 0 {
|
|
forceNewMac = true
|
|
} else {
|
|
macObjId = *req.MacId
|
|
}
|
|
}
|
|
|
|
newMac := ""
|
|
|
|
if forceNewMac || macObjId == 0 {
|
|
macAddress := utils.GenerateRandomMAC()
|
|
base := fmt.Sprintf("%s-%s", vm.Name, dbSwName)
|
|
name := base
|
|
|
|
for i := 0; ; i++ {
|
|
if i > 0 {
|
|
name = fmt.Sprintf("%s-%d", base, i)
|
|
}
|
|
|
|
var exists int64
|
|
if err := s.DB.
|
|
Model(&networkModels.Object{}).
|
|
Where("name = ?", name).
|
|
Limit(1).
|
|
Count(&exists).Error; err != nil {
|
|
return fmt.Errorf("failed_to_check_mac_object_exists: %w", err)
|
|
}
|
|
|
|
if exists == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
macObj := networkModels.Object{
|
|
Name: name,
|
|
Type: "Mac",
|
|
}
|
|
|
|
if err := s.DB.Create(&macObj).Error; err != nil {
|
|
return fmt.Errorf("failed_to_create_mac_object: %w", err)
|
|
}
|
|
|
|
macEntry := networkModels.ObjectEntry{
|
|
ObjectID: macObj.ID,
|
|
Value: macAddress,
|
|
}
|
|
|
|
if err := s.DB.Create(&macEntry).Error; err != nil {
|
|
return fmt.Errorf("failed_to_create_mac_entry: %w", err)
|
|
}
|
|
|
|
macObjId = macObj.ID
|
|
newMac = macAddress
|
|
} else {
|
|
var macObj networkModels.Object
|
|
if err := s.DB.Preload("Entries").First(&macObj, macObjId).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_mac_object: %w", err)
|
|
}
|
|
|
|
if macObj.Type != "Mac" {
|
|
return fmt.Errorf("invalid_mac_object_type: %s", macObj.Type)
|
|
}
|
|
|
|
if len(macObj.Entries) == 0 {
|
|
return fmt.Errorf("mac_object_has_no_entries: %d", macObjId)
|
|
}
|
|
|
|
var count int64
|
|
if err := s.DB.Model(&vmModels.Network{}).
|
|
Where("mac_id = ? AND id != ?", macObjId, network.ID).
|
|
Count(&count).Error; err != nil {
|
|
return fmt.Errorf("failed_to_find_other_networks_using_mac_object: %w", err)
|
|
}
|
|
|
|
if count > 0 {
|
|
return fmt.Errorf("mac_object_already_in_use: %d", macObjId)
|
|
}
|
|
|
|
newMac = macObj.Entries[0].Value
|
|
}
|
|
|
|
if strings.TrimSpace(newMac) == "" {
|
|
newMac = oldMac
|
|
}
|
|
|
|
xmlDesc, err := s.GetVMXML(vm.RID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_get_vm_xml: %w", err)
|
|
}
|
|
|
|
doc := etree.NewDocument()
|
|
if err := doc.ReadFromString(xmlDesc); err != nil {
|
|
return fmt.Errorf("failed_to_parse_vm_xml: %w", err)
|
|
}
|
|
|
|
var ifaceEl *etree.Element
|
|
for _, iface := range doc.FindElements("//interface[@type='bridge']") {
|
|
macEl := iface.FindElement("mac")
|
|
if macEl == nil {
|
|
continue
|
|
}
|
|
addrAttr := macEl.SelectAttr("address")
|
|
if addrAttr == nil {
|
|
continue
|
|
}
|
|
|
|
if strings.EqualFold(strings.TrimSpace(addrAttr.Value), oldMac) {
|
|
ifaceEl = iface
|
|
break
|
|
}
|
|
}
|
|
|
|
if ifaceEl == nil {
|
|
return fmt.Errorf("network_interface_not_found_in_xml: %s", oldMac)
|
|
}
|
|
|
|
macEl := ifaceEl.FindElement("mac")
|
|
if macEl == nil {
|
|
macEl = etree.NewElement("mac")
|
|
ifaceEl.AddChild(macEl)
|
|
}
|
|
if addrAttr := macEl.SelectAttr("address"); addrAttr != nil {
|
|
addrAttr.Value = newMac
|
|
} else {
|
|
macEl.CreateAttr("address", newMac)
|
|
}
|
|
|
|
sourceEl := ifaceEl.FindElement("source")
|
|
if sourceEl == nil {
|
|
sourceEl = etree.NewElement("source")
|
|
ifaceEl.AddChild(sourceEl)
|
|
}
|
|
if bridgeAttr := sourceEl.SelectAttr("bridge"); bridgeAttr != nil {
|
|
bridgeAttr.Value = bridgeName
|
|
} else {
|
|
sourceEl.CreateAttr("bridge", bridgeName)
|
|
}
|
|
|
|
modelEl := ifaceEl.FindElement("model")
|
|
if modelEl == nil {
|
|
modelEl = etree.NewElement("model")
|
|
ifaceEl.AddChild(modelEl)
|
|
}
|
|
if typeAttr := modelEl.SelectAttr("type"); typeAttr != nil {
|
|
typeAttr.Value = req.Emulation
|
|
} else {
|
|
modelEl.CreateAttr("type", req.Emulation)
|
|
}
|
|
|
|
newXML, err := doc.WriteToString()
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_serialize_modified_xml: %w", err)
|
|
}
|
|
|
|
domain, err := s.conn().DomainLookupByName(strconv.Itoa(int(vm.RID)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_lookup_domain_by_name: %w", err)
|
|
}
|
|
|
|
if err := s.conn().DomainUndefineFlags(domain, 0); err != nil {
|
|
return fmt.Errorf("failed_to_undefine_domain: %w", err)
|
|
}
|
|
|
|
if _, err := s.conn().DomainDefineXML(newXML); err != nil {
|
|
return fmt.Errorf("failed_to_define_domain_with_modified_xml: %w", err)
|
|
}
|
|
|
|
network.SwitchID = switchID
|
|
network.SwitchType = switchType
|
|
network.Emulation = req.Emulation
|
|
updates := map[string]any{
|
|
"switch_id": network.SwitchID,
|
|
"switch_type": network.SwitchType,
|
|
"emulation": network.Emulation,
|
|
}
|
|
if macObjId != 0 {
|
|
updates["mac_id"] = macObjId
|
|
} else {
|
|
updates["mac_id"] = nil
|
|
}
|
|
|
|
if err := s.DB.Model(&vmModels.Network{}).
|
|
Where("id = ?", network.ID).
|
|
Updates(updates).Error; err != nil {
|
|
return fmt.Errorf("failed_to_update_network_record: %w", err)
|
|
}
|
|
|
|
err = s.WriteVMJson(vm.RID)
|
|
if err != nil {
|
|
logger.L.Error().Err(err).Msg("Failed to write VM JSON after network update")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) FindAndChangeMAC(rid uint, oldMac string, newMac string) error {
|
|
if err := s.requireConnection(); err != nil {
|
|
return err
|
|
}
|
|
|
|
domain, err := s.conn().DomainLookupByName(strconv.Itoa(int(rid)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_lookup_domain_by_name: %w", err)
|
|
}
|
|
|
|
xml, err := s.conn().DomainGetXMLDesc(domain, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("failed_to_get_domain_xml_desc: %w", err)
|
|
}
|
|
|
|
doc := etree.NewDocument()
|
|
if err := doc.ReadFromString(xml); err != nil {
|
|
return fmt.Errorf("failed_to_parse_domain_xml: %w", err)
|
|
}
|
|
|
|
oldMac = strings.ToLower(oldMac)
|
|
newMac = strings.ToLower(newMac)
|
|
|
|
macEl := doc.FindElement("//mac[@address='" + oldMac + "']")
|
|
if macEl == nil {
|
|
return fmt.Errorf("mac_address_not_found_in_xml: %s", oldMac)
|
|
}
|
|
|
|
addrAttr := macEl.SelectAttr("address")
|
|
if addrAttr != nil {
|
|
addrAttr.Value = newMac
|
|
} else {
|
|
macEl.CreateAttr("address", newMac)
|
|
}
|
|
|
|
out, err := doc.WriteToString()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to serialize XML: %w", err)
|
|
}
|
|
|
|
if err := s.conn().DomainUndefineFlags(domain, 0); err != nil {
|
|
return fmt.Errorf("failed_to_undefine_domain: %w", err)
|
|
}
|
|
|
|
if _, err := s.conn().DomainDefineXML(out); err != nil {
|
|
return fmt.Errorf("failed_to_define_domain_with_modified_xml: %w", err)
|
|
}
|
|
|
|
err = s.WriteVMJson(rid)
|
|
if err != nil {
|
|
logger.L.Error().Err(err).Msg("Failed to write VM JSON after MAC modification")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) FindVmByMac(mac string) (vmModels.VM, error) {
|
|
mac = strings.ToLower(strings.TrimSpace(mac))
|
|
var netIf vmModels.Network
|
|
var vm vmModels.VM
|
|
|
|
err := s.DB.
|
|
Joins("LEFT JOIN objects ON networks.mac_id = objects.id").
|
|
Joins("LEFT JOIN object_entries ON object_entries.object_id = objects.id").
|
|
Where("LOWER(object_entries.value) = ?", mac).
|
|
First(&netIf).Error
|
|
if err != nil {
|
|
return vm, fmt.Errorf("failed_to_find_network: %w", err)
|
|
}
|
|
|
|
if err := s.DB.First(&vm, "id = ?", netIf.VMID).Error; err != nil {
|
|
return vm, fmt.Errorf("failed_to_find_vm: %w", err)
|
|
}
|
|
|
|
if vm.WoL == false {
|
|
return vm, fmt.Errorf("vm_wol_disabled: %s", vm.Name)
|
|
}
|
|
|
|
return vm, nil
|
|
}
|