Skip to content

Commit

Permalink
use bios uuid for providerID instead of vmid so we can easily generat…
Browse files Browse the repository at this point in the history
…e Node's providerID
  • Loading branch information
sp-yduck committed Jul 1, 2023
1 parent 3900255 commit a353841
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 154 deletions.
4 changes: 4 additions & 0 deletions api/v1beta1/proxmoxmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`

Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions cloud/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
41 changes: 16 additions & 25 deletions cloud/providerid/providerid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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://<node name>/<vmid>
return Prefix + path.Join(p.node, strconv.Itoa(p.vmid))
// provider ID : proxmox://<bios-uuid>
return Prefix + p.uuid
}
15 changes: 11 additions & 4 deletions cloud/scope/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand All @@ -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
}
Expand All @@ -182,15 +185,19 @@ 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
}
m.ProxmoxMachine.Spec.ProviderID = pointer.StringPtr(providerid.String())
return nil
}

func (m *MachineScope) SetVMID(vmid int) {
m.ProxmoxMachine.Spec.VMID = &vmid
}

func (m *MachineScope) SetReady() {
m.ProxmoxMachine.Status.Ready = true
}
Expand Down
152 changes: 152 additions & 0 deletions cloud/services/compute/instance/qemu.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit a353841

Please sign in to comment.