feat: custom instance type

Now, we can set a custom instance type using the smbios1[sku] argument

Signed-off-by: Serge Logvinov <serge.logvinov@sinextra.dev>
This commit is contained in:
Serge Logvinov
2025-02-13 17:22:44 +02:00
committed by Serge
parent 3a34fb960a
commit 0f0374c2eb
3 changed files with 109 additions and 36 deletions

View File

@@ -180,7 +180,7 @@ func (c *Cluster) FindVMByUUID(ctx context.Context, uuid string) (*pxapi.VmRef,
} }
if config["smbios1"] != nil { if config["smbios1"] != nil {
if c.getUUID(config["smbios1"].(string)) == uuid { //nolint:errcheck if c.getSMBSetting(config, "uuid") == uuid {
return vmr, region, nil return vmr, region, nil
} }
} }
@@ -202,13 +202,27 @@ func (c *Cluster) GetVMName(vmInfo map[string]interface{}) string {
// GetVMUUID returns the VM UUID. // GetVMUUID returns the VM UUID.
func (c *Cluster) GetVMUUID(vmInfo map[string]interface{}) string { func (c *Cluster) GetVMUUID(vmInfo map[string]interface{}) string {
if vmInfo["smbios1"] != nil { if vmInfo["smbios1"] != nil {
return c.getUUID(vmInfo["smbios1"].(string)) //nolint:errcheck return c.getSMBSetting(vmInfo, "uuid")
} }
return "" return ""
} }
func (c *Cluster) getUUID(smbios string) string { // GetVMSKU returns the VM instance type name.
func (c *Cluster) GetVMSKU(vmInfo map[string]interface{}) string {
if vmInfo["smbios1"] != nil {
return c.getSMBSetting(vmInfo, "sku")
}
return ""
}
func (c *Cluster) getSMBSetting(vmInfo map[string]interface{}, name string) string {
smbios, ok := vmInfo["smbios1"].(string)
if !ok {
return ""
}
for _, l := range strings.Split(smbios, ",") { for _, l := range strings.Split(smbios, ",") {
if l == "" || l == "base64=1" { if l == "" || l == "base64=1" {
continue continue
@@ -220,7 +234,7 @@ func (c *Cluster) getUUID(smbios string) string {
} }
for k, v := range parsedParameter { for k, v := range parsedParameter {
if k == "uuid" { if k == name {
decodedString, err := base64.StdEncoding.DecodeString(v[0]) decodedString, err := base64.StdEncoding.DecodeString(v[0])
if err != nil { if err != nil {
decodedString = []byte(v[0]) decodedString = []byte(v[0])

View File

@@ -19,6 +19,8 @@ package proxmox
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strconv"
"strings" "strings"
pxapi "github.com/Telmate/proxmox-api-go/proxmox" pxapi "github.com/Telmate/proxmox-api-go/proxmox"
@@ -38,6 +40,8 @@ type instances struct {
provider cluster.Provider provider cluster.Provider
} }
var instanceTypeNameRegexp = regexp.MustCompile(`(^[a-zA-Z0-9_.-]+)$`)
func newInstances(client *cluster.Cluster, provider cluster.Provider) *instances { func newInstances(client *cluster.Cluster, provider cluster.Provider) *instances {
return &instances{ return &instances{
c: client, c: client,
@@ -225,7 +229,7 @@ func (i *instances) getInstance(ctx context.Context, node *v1.Node) (*pxapi.VmRe
mc := metrics.NewMetricContext("getVmInfo") mc := metrics.NewMetricContext("getVmInfo")
vmInfo, err := px.GetVmInfo(ctx, vmRef) vmConfig, err := px.GetVmConfig(ctx, vmRef)
if mc.ObserveRequest(err) != nil { if mc.ObserveRequest(err) != nil {
if strings.Contains(err.Error(), "not found") { if strings.Contains(err.Error(), "not found") {
return nil, "", cloudprovider.InstanceNotFound return nil, "", cloudprovider.InstanceNotFound
@@ -234,13 +238,13 @@ func (i *instances) getInstance(ctx context.Context, node *v1.Node) (*pxapi.VmRe
return nil, "", err return nil, "", err
} }
if i.c.GetVMName(vmInfo) != node.Name && i.c.GetVMUUID(vmInfo) != node.Status.NodeInfo.SystemUUID { if i.c.GetVMName(vmConfig) != node.Name || i.c.GetVMUUID(vmConfig) != node.Status.NodeInfo.SystemUUID {
klog.Errorf("instances.getInstance() vm.name(%s) != node.name(%s) with uuid=%s", i.c.GetVMName(vmInfo), node.Name, node.Status.NodeInfo.SystemUUID) klog.Errorf("instances.getInstance() vm.name(%s) != node.name(%s) with uuid=%s", i.c.GetVMName(vmConfig), node.Name, node.Status.NodeInfo.SystemUUID)
return nil, "", cloudprovider.InstanceNotFound return nil, "", cloudprovider.InstanceNotFound
} }
klog.V(5).Infof("instances.getInstance() vmInfo %+v", vmInfo) klog.V(5).Infof("instances.getInstance() vmConfig %+v", vmConfig)
return vmRef, region, nil return vmRef, region, nil
} }
@@ -253,16 +257,26 @@ func (i *instances) getInstanceType(ctx context.Context, vmRef *pxapi.VmRef, reg
mc := metrics.NewMetricContext("getVmInfo") mc := metrics.NewMetricContext("getVmInfo")
vmInfo, err := px.GetVmInfo(ctx, vmRef) vmConfig, err := px.GetVmConfig(ctx, vmRef)
if mc.ObserveRequest(err) != nil { if mc.ObserveRequest(err) != nil {
return "", err return "", err
} }
if vmInfo["maxcpu"] == nil || vmInfo["maxmem"] == nil { sku := i.c.GetVMSKU(vmConfig)
if sku != "" && instanceTypeNameRegexp.MatchString(sku) {
return sku, nil
}
if vmConfig["cores"] == nil || vmConfig["memory"] == nil {
return "", fmt.Errorf("instances.getInstanceType() failed to get instance type") return "", fmt.Errorf("instances.getInstanceType() failed to get instance type")
} }
memory, err := strconv.Atoi(vmConfig["memory"].(string)) //nolint:errcheck
if err != nil {
return "", err
}
return fmt.Sprintf("%.0fVCPU-%.0fGB", return fmt.Sprintf("%.0fVCPU-%.0fGB",
vmInfo["maxcpu"].(float64), //nolint:errcheck vmConfig["cores"].(float64), //nolint:errcheck
vmInfo["maxmem"].(float64)/1024/1024/1024), nil //nolint:errcheck float64(memory)/1024), nil
} }

View File

@@ -66,22 +66,20 @@ clusters:
return httpmock.NewJsonResponse(200, map[string]interface{}{ return httpmock.NewJsonResponse(200, map[string]interface{}{
"data": []interface{}{ "data": []interface{}{
map[string]interface{}{ map[string]interface{}{
"node": "pve-1", "node": "pve-1",
"type": "qemu", "type": "qemu",
"vmid": 100, "vmid": 100,
"name": "cluster-1-node-1", "name": "cluster-1-node-1",
"maxcpu": 4, "maxcpu": 4,
"maxmem": 10 * 1024 * 1024 * 1024, "maxmem": 10 * 1024 * 1024 * 1024,
"smbios1": "uuid=8af7110d-bfad-407a-a663-9527d10a6583",
}, },
map[string]interface{}{ map[string]interface{}{
"node": "pve-2", "node": "pve-2",
"type": "qemu", "type": "qemu",
"vmid": 101, "vmid": 101,
"name": "cluster-1-node-2", "name": "cluster-1-node-2",
"maxcpu": 2, "maxcpu": 2,
"maxmem": 5 * 1024 * 1024 * 1024, "maxmem": 5 * 1024 * 1024 * 1024,
"smbios1": "uuid=5d04cb23-ea78-40a3-af2e-dd54798dc887",
}, },
}, },
}) })
@@ -93,13 +91,12 @@ clusters:
return httpmock.NewJsonResponse(200, map[string]interface{}{ return httpmock.NewJsonResponse(200, map[string]interface{}{
"data": []interface{}{ "data": []interface{}{
map[string]interface{}{ map[string]interface{}{
"node": "pve-3", "node": "pve-3",
"type": "qemu", "type": "qemu",
"vmid": 100, "vmid": 100,
"name": "cluster-2-node-1", "name": "cluster-2-node-1",
"maxcpu": 1, "maxcpu": 1,
"maxmem": 2 * 1024 * 1024 * 1024, "maxmem": 2 * 1024 * 1024 * 1024,
"smbios1": "uuid=3d3db687-89dd-473e-8463-6599f25b36a8",
}, },
}, },
}) })
@@ -110,9 +107,12 @@ clusters:
func(_ *http.Request) (*http.Response, error) { func(_ *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{ return httpmock.NewJsonResponse(200, map[string]interface{}{
"data": map[string]interface{}{ "data": map[string]interface{}{
"name": "cluster-1-node-1",
"node": "pve-1", "node": "pve-1",
"type": "qemu", "type": "qemu",
"vmid": 100, "vmid": 100,
"cores": 4,
"memory": "10240",
"smbios1": "uuid=8af7110d-bfad-407a-a663-9527d10a6583", "smbios1": "uuid=8af7110d-bfad-407a-a663-9527d10a6583",
}, },
}) })
@@ -123,9 +123,12 @@ clusters:
func(_ *http.Request) (*http.Response, error) { func(_ *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{ return httpmock.NewJsonResponse(200, map[string]interface{}{
"data": map[string]interface{}{ "data": map[string]interface{}{
"name": "cluster-1-node-2",
"node": "pve-2", "node": "pve-2",
"type": "qemu", "type": "qemu",
"vmid": 101, "vmid": 101,
"cores": 2,
"memory": "5120",
"smbios1": "uuid=5d04cb23-ea78-40a3-af2e-dd54798dc887", "smbios1": "uuid=5d04cb23-ea78-40a3-af2e-dd54798dc887",
}, },
}) })
@@ -136,10 +139,13 @@ clusters:
func(_ *http.Request) (*http.Response, error) { func(_ *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{ return httpmock.NewJsonResponse(200, map[string]interface{}{
"data": map[string]interface{}{ "data": map[string]interface{}{
"name": "cluster-2-node-1",
"node": "pve-3", "node": "pve-3",
"type": "qemu", "type": "qemu",
"vmid": 100, "vmid": 100,
"smbios1": "uuid=3d3db687-89dd-473e-8463-6599f25b36a8", "cores": 1,
"memory": "2048",
"smbios1": "uuid=3d3db687-89dd-473e-8463-6599f25b36a8,sku=YzEubWVkaXVt",
}, },
}) })
}, },
@@ -237,6 +243,11 @@ func (ts *ccmTestSuite) TestInstanceExists() {
Spec: v1.NodeSpec{ Spec: v1.NodeSpec{
ProviderID: "proxmox://cluster-1/100", ProviderID: "proxmox://cluster-1/100",
}, },
Status: v1.NodeStatus{
NodeInfo: v1.NodeSystemInfo{
SystemUUID: "8af7110d-bfad-407a-a663-9527d10a6583",
},
},
}, },
expected: true, expected: true,
}, },
@@ -255,7 +266,24 @@ func (ts *ccmTestSuite) TestInstanceExists() {
}, },
}, },
}, },
expected: true, expected: false,
},
{
msg: "NodeExistsWithDifferentUUID",
node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-1-node-1",
},
Spec: v1.NodeSpec{
ProviderID: "proxmox://cluster-1/100",
},
Status: v1.NodeStatus{
NodeInfo: v1.NodeSystemInfo{
SystemUUID: "8af7110d-0000-0000-0000-9527d10a6583",
},
},
},
expected: false,
}, },
{ {
msg: "NodeExistsWithDifferentNameAndUUID", msg: "NodeExistsWithDifferentNameAndUUID",
@@ -405,6 +433,23 @@ func (ts *ccmTestSuite) TestInstanceShutdown() {
}, },
expected: false, expected: false,
}, },
{
msg: "NodeExistsWithDifferentNameAndUUID",
node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-1-node-3",
},
Spec: v1.NodeSpec{
ProviderID: "proxmox://cluster-1/100",
},
Status: v1.NodeStatus{
NodeInfo: v1.NodeSystemInfo{
SystemUUID: "8af7110d-0000-0000-0000-9527d10a6583",
},
},
},
expected: false,
},
} }
for _, testCase := range tests { for _, testCase := range tests {
@@ -586,7 +631,7 @@ func (ts *ccmTestSuite) TestInstanceMetadata() {
Address: "cluster-2-node-1", Address: "cluster-2-node-1",
}, },
}, },
InstanceType: "1VCPU-2GB", InstanceType: "c1.medium",
Region: "cluster-2", Region: "cluster-2",
Zone: "pve-3", Zone: "pve-3",
}, },