From a3538416a0ddf75591f380face2740199ccddaa8 Mon Sep 17 00:00:00 2001 From: sp-yduck Date: Sat, 1 Jul 2023 16:01:08 +0900 Subject: [PATCH] use bios uuid for providerID instead of vmid so we can easily generate Node's providerID --- api/v1beta1/proxmoxmachine_types.go | 4 + api/v1beta1/zz_generated.deepcopy.go | 5 + cloud/interfaces.go | 6 +- cloud/providerid/providerid.go | 41 ++-- cloud/scope/machine.go | 15 +- cloud/services/compute/instance/qemu.go | 152 ++++++++++++ cloud/services/compute/instance/reconcile.go | 217 ++++++++---------- ...ture.cluster.x-k8s.io_proxmoxmachines.yaml | 3 + ...ster.x-k8s.io_proxmoxmachinetemplates.yaml | 3 + controllers/proxmoxmachine_controller.go | 12 +- 10 files changed, 304 insertions(+), 154 deletions(-) create mode 100644 cloud/services/compute/instance/qemu.go diff --git a/api/v1beta1/proxmoxmachine_types.go b/api/v1beta1/proxmoxmachine_types.go index d311ced..b5fe3bc 100644 --- a/api/v1beta1/proxmoxmachine_types.go +++ b/api/v1beta1/proxmoxmachine_types.go @@ -42,6 +42,10 @@ type ProxmoxMachineSpec struct { // +optional Node string `json:"node,omitempty"` + // VMID is proxmox qemu's id + // +optional + VMID *int `json:"vmID,omitempty"` + // Image is the image to be provisioned Image Image `json:"image"` diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 3826892..3ab1349 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -363,6 +363,11 @@ func (in *ProxmoxMachineSpec) DeepCopyInto(out *ProxmoxMachineSpec) { *out = new(string) **out = **in } + if in.VMID != nil { + in, out := &in.VMID, &out.VMID + *out = new(int) + **out = **in + } in.Image.DeepCopyInto(&out.Image) in.CloudInit.DeepCopyInto(&out.CloudInit) out.Hardware = in.Hardware diff --git a/cloud/interfaces.go b/cloud/interfaces.go index da6a2a6..d9a48fa 100644 --- a/cloud/interfaces.go +++ b/cloud/interfaces.go @@ -53,7 +53,7 @@ type MachineGetter interface { // IsControlPlane() bool // ControlPlaneGroupName() string NodeName() string - GetInstanceID() *string + GetBiosUUID() *string GetImage() infrav1.Image GetProviderID() string GetBootstrapData() (string, error) @@ -62,13 +62,15 @@ type MachineGetter interface { GetCloudInit() infrav1.CloudInit GetNetwork() infrav1.Network GetHardware() infrav1.Hardware + GetVMID() *int } // MachineSetter is an interface which can set machine information. type MachineSetter interface { - SetProviderID(node string, vmid int) error + SetProviderID(uuid string) error SetInstanceStatus(v infrav1.InstanceStatus) SetNodeName(name string) + SetVMID(vmid int) // SetFailureMessage(v error) // SetFailureReason(v capierrors.MachineStatusError) // SetAnnotation(key, value string) diff --git a/cloud/providerid/providerid.go b/cloud/providerid/providerid.go index 64dcdfd..67a56c8 100644 --- a/cloud/providerid/providerid.go +++ b/cloud/providerid/providerid.go @@ -2,50 +2,41 @@ package providerid import ( "fmt" - "path" - "strconv" "github.com/pkg/errors" ) -const Prefix = "proxmox://" +const ( + Prefix = "proxmox://" + UUIDFormat = `[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}` +) type ProviderID interface { - Node() string - VMID() int + UUID() string fmt.Stringer } type providerID struct { - // proxmox node name - node string - // proxmox vmid - vmid int + uuid string } -func New(node string, vmid int) (ProviderID, error) { - if node == "" { - return nil, errors.New("location required for provider id") - } - if vmid == 0 { - return nil, errors.New("vmid required for provider id") +func New(uuid string) (ProviderID, error) { + if uuid == "" { + return nil, errors.New("uuid is required for provider id") } + // to do: validate uuid + return &providerID{ - node: node, - vmid: vmid, + uuid: uuid, }, nil } -func (p *providerID) Node() string { - return p.node -} - -func (p *providerID) VMID() int { - return p.vmid +func (p *providerID) UUID() string { + return p.uuid } func (p *providerID) String() string { - // provider ID : proxmox:/// - return Prefix + path.Join(p.node, strconv.Itoa(p.vmid)) + // provider ID : proxmox:// + return Prefix + p.uuid } diff --git a/cloud/scope/machine.go b/cloud/scope/machine.go index 9c7b188..79771fc 100644 --- a/cloud/scope/machine.go +++ b/cloud/scope/machine.go @@ -142,12 +142,11 @@ func (m *MachineScope) SetInstanceStatus(v infrav1.InstanceStatus) { m.ProxmoxMachine.Status.InstanceStatus = &v } -func (m *MachineScope) GetInstanceID() *string { +func (m *MachineScope) GetBiosUUID() *string { parsed, err := noderefutil.NewProviderID(m.GetProviderID()) if err != nil { return nil } - // instance id == vmid return pointer.StringPtr(parsed.ID()) } @@ -158,6 +157,10 @@ func (m *MachineScope) GetProviderID() string { return "" } +func (m *MachineScope) GetVMID() *int { + return m.ProxmoxMachine.Spec.VMID +} + func (m *MachineScope) GetImage() infrav1.Image { return m.ProxmoxMachine.Spec.Image } @@ -182,8 +185,8 @@ func (m *MachineScope) GetHardware() infrav1.Hardware { } // SetProviderID sets the ProxmoxMachine providerID in spec. -func (m *MachineScope) SetProviderID(node string, vmid int) error { - providerid, err := providerid.New(node, vmid) +func (m *MachineScope) SetProviderID(uuid string) error { + providerid, err := providerid.New(uuid) if err != nil { return err } @@ -191,6 +194,10 @@ func (m *MachineScope) SetProviderID(node string, vmid int) error { return nil } +func (m *MachineScope) SetVMID(vmid int) { + m.ProxmoxMachine.Spec.VMID = &vmid +} + func (m *MachineScope) SetReady() { m.ProxmoxMachine.Status.Ready = true } diff --git a/cloud/services/compute/instance/qemu.go b/cloud/services/compute/instance/qemu.go new file mode 100644 index 0000000..1b915a7 --- /dev/null +++ b/cloud/services/compute/instance/qemu.go @@ -0,0 +1,152 @@ +package instance + +import ( + "context" + "fmt" + "math/rand" + "net/url" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/sp-yduck/proxmox/pkg/api" + "github.com/sp-yduck/proxmox/pkg/service/node" + "github.com/sp-yduck/proxmox/pkg/service/node/vm" + "sigs.k8s.io/controller-runtime/pkg/log" + + infrav1 "github.com/sp-yduck/cluster-api-provider-proxmox/api/v1beta1" +) + +func (s *Service) reconcileQEMU(ctx context.Context) (*vm.VirtualMachine, error) { + log := log.FromContext(ctx) + log.Info("Reconciling QEMU") + + nodeName := s.scope.NodeName() + vmid := s.scope.GetVMID() + vm, err := s.getQEMU(nodeName, vmid) + if err == nil { // if vm is found, return it + return vm, nil + } + if !IsNotFound(err) { + log.Error(err, fmt.Sprintf("failed to get vm: node=%s,vmid=%d", nodeName, *vmid)) + return nil, err + } + + // no vm found, create new one + return s.createQEMU(ctx, nodeName, vmid) +} + +func (s *Service) getQEMU(nodeName string, vmid *int) (*vm.VirtualMachine, error) { + if vmid != nil && nodeName != "" { + node, err := s.GetNode(nodeName) + if err != nil { + return nil, err + } + return node.VirtualMachine(*vmid) + } + return nil, api.ErrNotFound +} + +func (s *Service) createQEMU(ctx context.Context, nodeName string, vmid *int) (*vm.VirtualMachine, error) { + log := log.FromContext(ctx) + + var node *node.Node + var err error + + // get node + if nodeName != "" { + node, err = s.GetNode(nodeName) + if err != nil { + log.Error(err, fmt.Sprintf("failed to get node %s", nodeName)) + return nil, err + } + } else { + // temp solution + node, err = s.GetRandomNode() + if err != nil { + log.Error(err, "failed to get random node") + return nil, err + } + s.scope.SetNodeName(node.Node) + } + + // (for multiple node proxmox cluster support) + // to do : set ssh client for specific node + + // if vmid is empty, generate new vmid + if vmid == nil { + nextid, err := s.GetNextID() + if err != nil { + log.Error(err, "failed to get available vmid") + return nil, err + } + vmid = &nextid + } + + // create vm + vmoption := generateVMOptions(s.scope.Name(), s.scope.GetStorage().Name, s.scope.GetNetwork(), s.scope.GetHardware()) + vm, err := node.CreateVirtualMachine(*vmid, vmoption) + if err != nil { + log.Error(err, "failed to create virtual machine") + return nil, err + } + s.scope.SetVMID(*vmid) + return vm, nil +} + +func (s *Service) GetNextID() (int, error) { + return s.client.NextID() +} + +func (s *Service) GetNodes() ([]*node.Node, error) { + return s.client.Nodes() +} + +func (s *Service) GetNode(name string) (*node.Node, error) { + return s.client.Node(name) +} + +// GetRandomNode returns a node chosen randomly +func (s *Service) GetRandomNode() (*node.Node, error) { + nodes, err := s.GetNodes() + if err != nil { + return nil, err + } + if len(nodes) <= 0 { + return nil, errors.Errorf("no nodes found") + } + src := rand.NewSource(time.Now().Unix()) + r := rand.New(src) + return nodes[r.Intn(len(nodes))], nil +} + +func generateVMOptions(vmName, storageName string, network infrav1.Network, hardware infrav1.Hardware) vm.VirtualMachineCreateOptions { + vmoptions := vm.VirtualMachineCreateOptions{ + Agent: "enabled=1", + Cores: hardware.CPU, + Memory: hardware.Memory, + Name: vmName, + NameServer: network.NameServer, + Boot: "order=scsi0", + Ide: vm.Ide{Ide2: fmt.Sprintf("file=%s:cloudinit,media=cdrom", storageName)}, + CiCustom: fmt.Sprintf("user=%s:snippets/%s-user.yml", storageName, vmName), + IPConfig: vm.IPConfig{IPConfig0: network.IPConfig.String()}, + OSType: vm.L26, + Net: vm.Net{Net0: "model=virtio,bridge=vmbr0,firewall=1"}, + Scsi: vm.Scsi{Scsi0: fmt.Sprintf("file=%s:8", storageName)}, + ScsiHw: vm.VirtioScsiPci, + SearchDomain: network.SearchDomain, + Serial: vm.Serial{Serial0: "socket"}, + VGA: "serial0", + } + return vmoptions +} + +// URL encodes the ssh keys +func sshKeyUrlEncode(keys string) (encodedKeys string) { + encodedKeys = url.PathEscape(keys + "\n") + encodedKeys = strings.Replace(encodedKeys, "+", "%2B", -1) + encodedKeys = strings.Replace(encodedKeys, "@", "%40", -1) + encodedKeys = strings.Replace(encodedKeys, "=", "%3D", -1) + return +} diff --git a/cloud/services/compute/instance/reconcile.go b/cloud/services/compute/instance/reconcile.go index 5924d8b..8a01cb7 100644 --- a/cloud/services/compute/instance/reconcile.go +++ b/cloud/services/compute/instance/reconcile.go @@ -3,20 +3,19 @@ package instance import ( "context" "fmt" - "math/rand" - "net/url" "path" + "regexp" "strconv" "strings" - "time" "github.com/pkg/errors" "github.com/sp-yduck/proxmox/pkg/api" - "github.com/sp-yduck/proxmox/pkg/service/node" "github.com/sp-yduck/proxmox/pkg/service/node/vm" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/log" infrav1 "github.com/sp-yduck/cluster-api-provider-proxmox/api/v1beta1" + "github.com/sp-yduck/cluster-api-provider-proxmox/cloud/providerid" "github.com/sp-yduck/cluster-api-provider-proxmox/cloud/scope" ) @@ -24,22 +23,49 @@ const ( etcCAPP = "/etc/capp" ) +// reconcile normal func (s *Service) Reconcile(ctx context.Context) error { log := log.FromContext(ctx) - log.Info("Reconciling instance resources") + log.Info("Reconciling instance") instance, err := s.createOrGetInstance(ctx) if err != nil { log.Error(err, "failed to create/get instance") return err } - log.Info(fmt.Sprintf("instance : %v", instance)) - s.scope.SetProviderID(instance.Node.Name(), instance.VMID) + uuid, err := getBiosUUID(instance) + if err != nil { + return err + } + + log.Info(fmt.Sprintf("Reconciled instance: bios-uuid=%s", *uuid)) + s.scope.SetProviderID(*uuid) s.scope.SetInstanceStatus(infrav1.InstanceStatus(instance.Status)) // s.scope.SetAddresses() return nil } +// reconcile delete +func (s *Service) Delete(ctx context.Context) error { + log := log.FromContext(ctx) + log.Info("Deleting instance resources") + + instance, err := s.GetInstance(ctx) + if err != nil { + if !IsNotFound(err) { + return err + } + return nil + } + + // must stop or pause instance before deletion + // otherwise deletion will be fail + if err := EnsureStoppedOrPaused(*instance); err != nil { + return err + } + return instance.Delete() +} + func (s *Service) createOrGetInstance(ctx context.Context) (*vm.VirtualMachine, error) { log := log.FromContext(ctx) @@ -50,10 +76,11 @@ func (s *Service) createOrGetInstance(ctx context.Context) (*vm.VirtualMachine, return nil, errors.Wrap(err, "failed to retrieve bootstrap data") } - if s.scope.GetInstanceID() == nil { - log.Info("ProxmoxMachine doesn't have instanceID. instance will be created") + if s.scope.GetBiosUUID() == nil { + log.Info("ProxmoxMachine doesn't have bios UUID. instance will be created") return s.CreateInstance(ctx, bootstrapData) } + instance, err := s.GetInstance(ctx) if err != nil { if IsNotFound(err) { @@ -63,24 +90,52 @@ func (s *Service) createOrGetInstance(ctx context.Context) (*vm.VirtualMachine, log.Error(err, "failed to get instance") return nil, err } + return instance, nil } func (s *Service) GetInstance(ctx context.Context) (*vm.VirtualMachine, error) { log := log.FromContext(ctx) - instanceID := s.scope.GetInstanceID() - vm, err := s.getInstanceFromInstanceID(*instanceID) + biosUUID := s.scope.GetBiosUUID() + if biosUUID == nil { + return nil, api.ErrNotFound + } + vm, err := s.getInstanceFromBiosUUID(*biosUUID) if err != nil { if api.IsNotFound(err) { log.Info("instance wasn't found") return nil, api.ErrNotFound } - log.Error(err, "failed to get instance from instance ID") + log.Error(err, "failed to get instance from bios UUID") return nil, err } return vm, nil } +func getBiosUUID(vm *vm.VirtualMachine) (*string, error) { + config, err := vm.Config() + if err != nil { + return nil, err + } + smbios := config.SMBios1 + uuid, err := convertSMBiosToUUID(smbios) + if err != nil { + return nil, err + } + return pointer.StringPtr(uuid), nil +} + +func convertSMBiosToUUID(smbios string) (string, error) { + re := regexp.MustCompile(fmt.Sprintf("uuid=%s", providerid.UUIDFormat)) + match := re.FindString(smbios) + if match == "" { + return "", errors.Errorf("failed to fetch uuid form smbios") + } + // match: uuid= + return strings.Split(match, "=")[1], nil +} + +// will be abolished func (s *Service) getInstanceFromInstanceID(instanceID string) (*vm.VirtualMachine, error) { vmid, err := strconv.Atoi(instanceID) if err != nil { @@ -103,44 +158,48 @@ func (s *Service) getInstanceFromInstanceID(instanceID string) (*vm.VirtualMachi return nil, api.ErrNotFound } -func (s *Service) CreateInstance(ctx context.Context, bootstrap string) (*vm.VirtualMachine, error) { - log := log.FromContext(ctx) +func (s *Service) getInstanceFromBiosUUID(uuid string) (*vm.VirtualMachine, error) { + nodes, err := s.client.Nodes() + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, errors.New("proxmox nodes not found") + } - var node *node.Node - var err error - nodeName := s.scope.NodeName() - if nodeName != "" { - node, err = s.GetNode(nodeName) + // to do : check each node in parallel + for _, node := range nodes { + vms, err := node.VirtualMachines() if err != nil { - log.Error(err, fmt.Sprintf("failed to get node %s", nodeName)) - return nil, err + continue } - } else { - // temp solution - node, err = s.GetRandomNode() - if err != nil { - log.Error(err, "failed to get random node") - return nil, err + for _, vm := range vms { + config, err := vm.Config() + if err != nil { + return nil, err + } + vmuuid, err := convertSMBiosToUUID(config.SMBios1) + if err != nil { + return nil, err + } + if vmuuid == uuid { + return vm, nil + } } - s.scope.SetNodeName(node.Node) } + return nil, api.ErrNotFound +} - // (for multiple node proxmox cluster support) - // to do : set ssh client for specific node - - vmid, err := s.GetNextID() - if err != nil { - log.Error(err, "failed to get available vmid") - return nil, err - } +func (s *Service) CreateInstance(ctx context.Context, bootstrap string) (*vm.VirtualMachine, error) { + log := log.FromContext(ctx) - // create vm - vmoption := generateVMOptions(s.scope.Name(), s.scope.GetStorage().Name, s.scope.GetNetwork(), s.scope.GetHardware()) - vm, err := node.CreateVirtualMachine(vmid, vmoption) + // qemu + vm, err := s.reconcileQEMU(ctx) if err != nil { - log.Error(err, "failed to create virtual machine") return nil, err } + vmid := vm.VMID + log.Info(fmt.Sprintf("reconciled qemu: node=%s,vmid=%d", vm.Node.Name(), vmid)) // cloud init if err := reconcileCloudInit(s, vmid, bootstrap); err != nil { @@ -153,6 +212,7 @@ func (s *Service) CreateInstance(ctx context.Context, bootstrap string) (*vm.Vir } // volume + // to do: size option if err := vm.ResizeVolume("scsi0", "+30G"); err != nil { return nil, err } @@ -168,52 +228,6 @@ func IsNotFound(err error) bool { return api.IsNotFound(err) } -func (s *Service) GetNextID() (int, error) { - return s.client.NextID() -} - -func (s *Service) GetNodes() ([]*node.Node, error) { - return s.client.Nodes() -} - -func (s *Service) GetNode(name string) (*node.Node, error) { - return s.client.Node(name) -} - -// GetRandomNode returns a node chosen randomly -func (s *Service) GetRandomNode() (*node.Node, error) { - nodes, err := s.GetNodes() - if err != nil { - return nil, err - } - if len(nodes) <= 0 { - return nil, errors.Errorf("no nodes found") - } - src := rand.NewSource(time.Now().Unix()) - r := rand.New(src) - return nodes[r.Intn(len(nodes))], nil -} - -func (s *Service) Delete(ctx context.Context) error { - log := log.FromContext(ctx) - log.Info("Deleting instance resources") - - instance, err := s.GetInstance(ctx) - if err != nil { - if !IsNotFound(err) { - return err - } - return nil - } - - // must stop or pause instance before deletion - // otherwise deletion will be fail - if err := EnsureStoppedOrPaused(*instance); err != nil { - return err - } - return instance.Delete() -} - // setCloudImage set OS image to specified storage func SetCloudImage(ctx context.Context, vmid int, storage infrav1.Storage, image infrav1.Image, ssh scope.SSHClient) error { log := log.FromContext(ctx) @@ -239,37 +253,6 @@ func SetCloudImage(ctx context.Context, vmid int, storage infrav1.Storage, image return nil } -func generateVMOptions(vmName, storageName string, network infrav1.Network, hardware infrav1.Hardware) vm.VirtualMachineCreateOptions { - vmoptions := vm.VirtualMachineCreateOptions{ - Agent: "enabled=1", - Cores: hardware.CPU, - Memory: hardware.Memory, - Name: vmName, - NameServer: network.NameServer, - Boot: "order=scsi0", - Ide: vm.Ide{Ide2: fmt.Sprintf("file=%s:cloudinit,media=cdrom", storageName)}, - CiCustom: fmt.Sprintf("user=%s:snippets/%s-user.yml", storageName, vmName), - IPConfig: vm.IPConfig{IPConfig0: network.IPConfig.String()}, - OSType: vm.L26, - Net: vm.Net{Net0: "model=virtio,bridge=vmbr0,firewall=1"}, - Scsi: vm.Scsi{Scsi0: fmt.Sprintf("file=%s:8", storageName)}, - ScsiHw: vm.VirtioScsiPci, - SearchDomain: network.SearchDomain, - Serial: vm.Serial{Serial0: "socket"}, - VGA: "serial0", - } - return vmoptions -} - -// URL encodes the ssh keys -func sshKeyUrlEncode(keys string) (encodedKeys string) { - encodedKeys = url.PathEscape(keys + "\n") - encodedKeys = strings.Replace(encodedKeys, "+", "%2B", -1) - encodedKeys = strings.Replace(encodedKeys, "@", "%40", -1) - encodedKeys = strings.Replace(encodedKeys, "=", "%3D", -1) - return -} - func EnsureRunning(instance vm.VirtualMachine) error { // ensure instance is running switch instance.Status { diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachines.yaml index 9cbbf3a..063e4fb 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachines.yaml @@ -125,6 +125,9 @@ spec: providerID: description: ProviderID type: string + vmID: + description: VMID is proxmox qemu's id + type: integer required: - image type: object diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachinetemplates.yaml index a3b52b0..72c2a4f 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachinetemplates.yaml @@ -163,6 +163,9 @@ spec: providerID: description: ProviderID type: string + vmID: + description: VMID is proxmox qemu's id + type: integer required: - image type: object diff --git a/controllers/proxmoxmachine_controller.go b/controllers/proxmoxmachine_controller.go index 4237e45..8bda1e3 100644 --- a/controllers/proxmoxmachine_controller.go +++ b/controllers/proxmoxmachine_controller.go @@ -163,18 +163,18 @@ func (r *ProxmoxMachineReconciler) reconcile(ctx context.Context, machineScope * instanceState := *machineScope.GetInstanceStatus() switch instanceState { case infrav1.InstanceStatusRunning: - log.Info("ProxmoxMachine instance is running", "instance-id", *machineScope.GetInstanceID()) - record.Eventf(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "ProxmoxMachine instance is running - instance-id: %s", *machineScope.GetInstanceID()) + log.Info("ProxmoxMachine instance is running", "bios-uuid", *machineScope.GetBiosUUID()) + record.Eventf(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "ProxmoxMachine instance is running - bios-uuid: %s", *machineScope.GetBiosUUID()) record.Event(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "Reconciled") machineScope.SetReady() return ctrl.Result{}, nil case infrav1.InstanceStatusStopped: - log.Info("ProxmoxMachine instance is stopped", "instance-id", *machineScope.GetInstanceID()) - record.Eventf(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "ProxmoxMachine instance is stopped - instance-id: %s", *machineScope.GetInstanceID()) + log.Info("ProxmoxMachine instance is stopped", "instance-id", *machineScope.GetBiosUUID()) + record.Eventf(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "ProxmoxMachine instance is stopped - bios-uuid: %s", *machineScope.GetBiosUUID()) return ctrl.Result{RequeueAfter: 5 * time.Second}, nil case infrav1.InstanceStatusPaused: - log.Info("ProxmoxMachine instance is paused", "instance-id", *machineScope.GetInstanceID()) - record.Eventf(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "ProxmoxMachine instance is paused - instance-id: %s", *machineScope.GetInstanceID()) + log.Info("ProxmoxMachine instance is paused", "instance-id", *machineScope.GetBiosUUID()) + record.Eventf(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "ProxmoxMachine instance is paused - bios-uuid: %s", *machineScope.GetBiosUUID()) return ctrl.Result{RequeueAfter: 5 * time.Second}, nil default: machineScope.SetFailureReason(capierrors.UpdateMachineError)