From 5876cd4c7be105f50a1a279aacf5464a78c490d2 Mon Sep 17 00:00:00 2001 From: Serge Logvinov Date: Fri, 13 Sep 2024 15:05:49 +0300 Subject: [PATCH] feat: find node by uuid In some setups, the Proxmox VM name may differ from the Linux hostname. To reliably identify a VM within a Proxmox cluster, we can use the system's UUID Signed-off-by: Serge Logvinov --- go.sum | 10 ++---- pkg/cluster/client.go | 66 ++++++++++++++++++++++++++++++++++++++++ pkg/proxmox/instances.go | 23 ++++++++++++-- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/go.sum b/go.sum index 879d694..e513085 100644 --- a/go.sum +++ b/go.sum @@ -135,8 +135,6 @@ github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0q github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= @@ -216,8 +214,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -228,8 +224,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -255,8 +249,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/cluster/client.go b/pkg/cluster/client.go index 375bb53..71be3a2 100644 --- a/pkg/cluster/client.go +++ b/pkg/cluster/client.go @@ -19,8 +19,10 @@ package cluster import ( "crypto/tls" + "encoding/base64" "fmt" "net/http" + "net/url" "os" "strings" @@ -107,3 +109,67 @@ func (c *Cluster) FindVMByName(name string) (*pxapi.VmRef, string, error) { return nil, "", fmt.Errorf("vm '%s' not found", name) } + +// FindVMByUUID find a VM by uuid in all Proxmox clusters. +func (c *Cluster) FindVMByUUID(uuid string) (*pxapi.VmRef, string, error) { + for region, px := range c.proxmox { + vms, err := px.GetResourceList("vm") + if err != nil { + return nil, "", fmt.Errorf("error get resources %v", err) + } + + for vmii := range vms { + vm, ok := vms[vmii].(map[string]interface{}) + if !ok { + return nil, "", fmt.Errorf("failed to cast response to map, vm: %v", vm) + } + + if vm["type"].(string) != "qemu" { + continue + } + + vmr := pxapi.NewVmRef(int(vm["vmid"].(float64))) + vmr.SetNode(vm["node"].(string)) + vmr.SetVmType("qemu") + + config, err := px.GetVmConfig(vmr) + if err != nil { + return nil, "", err + } + + if config["smbios1"] != nil { + if c.getUUID(config["smbios1"].(string)) == uuid { + return vmr, region, nil + } + } + } + } + + return nil, "", fmt.Errorf("vm with uuid '%s' not found", uuid) +} + +func (c *Cluster) getUUID(smbios string) string { + for _, l := range strings.Split(smbios, ",") { + if l == "" || l == "base64=1" { + continue + } + + parsedParameter, err := url.ParseQuery(l) + if err != nil { + return "" + } + + for k, v := range parsedParameter { + if k == "uuid" { + decodedString, err := base64.StdEncoding.DecodeString(v[0]) + if err != nil { + decodedString = []byte(v[0]) + } + + return string(decodedString) + } + } + } + + return "" +} diff --git a/pkg/proxmox/instances.go b/pkg/proxmox/instances.go index 851587b..4da6dd9 100644 --- a/pkg/proxmox/instances.go +++ b/pkg/proxmox/instances.go @@ -48,6 +48,12 @@ func newInstances(client *cluster.Cluster) *instances { func (i *instances) InstanceExists(_ context.Context, node *v1.Node) (bool, error) { klog.V(4).InfoS("instances.InstanceExists() called", "node", klog.KRef("", node.Name)) + if node.Spec.ProviderID == "" { + klog.V(4).InfoS("instances.InstanceExists() empty providerID, omitting unmanaged node", "node", klog.KObj(node)) + + return true, nil + } + if !strings.HasPrefix(node.Spec.ProviderID, provider.ProviderName) { klog.V(4).InfoS("instances.InstanceExists() omitting unmanaged node", "node", klog.KObj(node), "providerID", node.Spec.ProviderID) @@ -73,6 +79,12 @@ func (i *instances) InstanceExists(_ context.Context, node *v1.Node) (bool, erro func (i *instances) InstanceShutdown(_ context.Context, node *v1.Node) (bool, error) { klog.V(4).InfoS("instances.InstanceShutdown() called", "node", klog.KRef("", node.Name)) + if node.Spec.ProviderID == "" { + klog.V(4).InfoS("instances.InstanceShutdown() empty providerID, omitting unmanaged node", "node", klog.KObj(node)) + + return false, nil + } + if !strings.HasPrefix(node.Spec.ProviderID, provider.ProviderName) { klog.V(4).InfoS("instances.InstanceShutdown() omitting unmanaged node", "node", klog.KObj(node), "providerID", node.Spec.ProviderID) @@ -122,13 +134,20 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr providerID := node.Spec.ProviderID if providerID == "" { - klog.V(4).InfoS("instances.InstanceMetadata() empty providerID, trying find by Name", "node", klog.KObj(node)) + uuid := node.Status.NodeInfo.SystemUUID + + klog.V(4).InfoS("instances.InstanceMetadata() empty providerID, trying find node", "node", klog.KObj(node), "uuid", uuid) mc := metrics.NewMetricContext("findVmByName") vmRef, region, err = i.c.FindVMByName(node.Name) if mc.ObserveRequest(err) != nil { - return nil, fmt.Errorf("instances.InstanceMetadata() - failed to find instance by name %s: %v, skipped", node.Name, err) + mc := metrics.NewMetricContext("findVmByUUID") + + vmRef, region, err = i.c.FindVMByUUID(uuid) + if mc.ObserveRequest(err) != nil { + return nil, fmt.Errorf("instances.InstanceMetadata() - failed to find instance by name/uuid %s: %v, skipped", node.Name, err) + } } } else if !strings.HasPrefix(node.Spec.ProviderID, provider.ProviderName) { klog.V(4).InfoS("instances.InstanceMetadata() omitting unmanaged node", "node", klog.KObj(node), "providerID", node.Spec.ProviderID)