diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7a590ba..af7f833 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,7 @@ ## Note to the Contributor We encourage contributors to go through a proposal process to discuss major changes. -Before your PR is allowed to run through CI, the maintainers of Talos CCM will first have to approve the PR. +Before your PR is allowed to run through CI, the maintainers of Proxmox CCM will first have to approve the PR. --> ## What? (description) diff --git a/pkg/cluster/client.go b/pkg/cluster/client.go index f9e0c8d..558a108 100644 --- a/pkg/cluster/client.go +++ b/pkg/cluster/client.go @@ -29,6 +29,7 @@ import ( pxapi "github.com/Telmate/proxmox-api-go/proxmox" + v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" ) @@ -106,6 +107,33 @@ func (c *Cluster) GetProxmoxCluster(region string) (*pxapi.Client, error) { return nil, fmt.Errorf("proxmox cluster %s not found", region) } +// FindVMByNode find a VM by kubernetes node resource in all Proxmox clusters. +func (c *Cluster) FindVMByNode(ctx context.Context, node *v1.Node) (*pxapi.VmRef, string, error) { + for region, px := range c.proxmox { + vmrs, err := px.GetVmRefsByName(ctx, node.Name) + if err != nil { + if strings.Contains(err.Error(), "not found") { + continue + } + + return nil, "", err + } + + for _, vmr := range vmrs { + config, err := px.GetVmConfig(ctx, vmr) + if err != nil { + return nil, "", err + } + + if c.GetVMUUID(config) == node.Status.NodeInfo.SystemUUID { + return vmr, region, nil + } + } + } + + return nil, "", fmt.Errorf("vm '%s' not found", node.Name) +} + // FindVMByName find a VM by name in all Proxmox clusters. func (c *Cluster) FindVMByName(ctx context.Context, name string) (*pxapi.VmRef, string, error) { for region, px := range c.proxmox { @@ -162,6 +190,24 @@ func (c *Cluster) FindVMByUUID(ctx context.Context, uuid string) (*pxapi.VmRef, return nil, "", fmt.Errorf("vm with uuid '%s' not found", uuid) } +// GetVMName returns the VM name. +func (c *Cluster) GetVMName(vmInfo map[string]interface{}) string { + if vmInfo["name"] != nil { + return vmInfo["name"].(string) //nolint:errcheck + } + + return "" +} + +// GetVMUUID returns the VM UUID. +func (c *Cluster) GetVMUUID(vmInfo map[string]interface{}) string { + if vmInfo["smbios1"] != nil { + return c.getUUID(vmInfo["smbios1"].(string)) //nolint:errcheck + } + + return "" +} + func (c *Cluster) getUUID(smbios string) string { for _, l := range strings.Split(smbios, ",") { if l == "" || l == "base64=1" { diff --git a/pkg/proxmox/instances.go b/pkg/proxmox/instances.go index aceec8a..62923ad 100644 --- a/pkg/proxmox/instances.go +++ b/pkg/proxmox/instances.go @@ -142,7 +142,7 @@ func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud mc := metrics.NewMetricContext("findVmByName") - vmRef, region, err = i.c.FindVMByName(ctx, node.Name) + vmRef, region, err = i.c.FindVMByNode(ctx, node) if mc.ObserveRequest(err) != nil { mc := metrics.NewMetricContext("findVmByUUID") @@ -234,8 +234,10 @@ func (i *instances) getInstance(ctx context.Context, node *v1.Node) (*pxapi.VmRe return nil, "", err } - if vmInfo["name"] != nil && vmInfo["name"].(string) != node.Name { //nolint:errcheck - return nil, "", fmt.Errorf("instances.getInstance() vm.name(%s) != node.name(%s)", vmInfo["name"].(string), node.Name) //nolint:errcheck + if i.c.GetVMName(vmInfo) != node.Name && i.c.GetVMUUID(vmInfo) != 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) + + return nil, "", cloudprovider.InstanceNotFound } klog.V(5).Infof("instances.getInstance() vmInfo %+v", vmInfo) diff --git a/pkg/proxmox/instances_test.go b/pkg/proxmox/instances_test.go index cb78694..a4406dd 100644 --- a/pkg/proxmox/instances_test.go +++ b/pkg/proxmox/instances_test.go @@ -66,20 +66,22 @@ clusters: return httpmock.NewJsonResponse(200, map[string]interface{}{ "data": []interface{}{ map[string]interface{}{ - "node": "pve-1", - "type": "qemu", - "vmid": 100, - "name": "cluster-1-node-1", - "maxcpu": 4, - "maxmem": 10 * 1024 * 1024 * 1024, + "node": "pve-1", + "type": "qemu", + "vmid": 100, + "name": "cluster-1-node-1", + "maxcpu": 4, + "maxmem": 10 * 1024 * 1024 * 1024, + "smbios1": "uuid=8af7110d-bfad-407a-a663-9527d10a6583", }, map[string]interface{}{ - "node": "pve-2", - "type": "qemu", - "vmid": 101, - "name": "cluster-1-node-2", - "maxcpu": 2, - "maxmem": 5 * 1024 * 1024 * 1024, + "node": "pve-2", + "type": "qemu", + "vmid": 101, + "name": "cluster-1-node-2", + "maxcpu": 2, + "maxmem": 5 * 1024 * 1024 * 1024, + "smbios1": "uuid=5d04cb23-ea78-40a3-af2e-dd54798dc887", }, }, }) @@ -91,18 +93,58 @@ clusters: return httpmock.NewJsonResponse(200, map[string]interface{}{ "data": []interface{}{ map[string]interface{}{ - "node": "pve-3", - "type": "qemu", - "vmid": 100, - "name": "cluster-2-node-1", - "maxcpu": 1, - "maxmem": 2 * 1024 * 1024 * 1024, + "node": "pve-3", + "type": "qemu", + "vmid": 100, + "name": "cluster-2-node-1", + "maxcpu": 1, + "maxmem": 2 * 1024 * 1024 * 1024, + "smbios1": "uuid=3d3db687-89dd-473e-8463-6599f25b36a8", }, }, }) }, ) + httpmock.RegisterResponder("GET", "https://127.0.0.1:8006/api2/json/nodes/pve-1/qemu/100/config", + func(_ *http.Request) (*http.Response, error) { + return httpmock.NewJsonResponse(200, map[string]interface{}{ + "data": map[string]interface{}{ + "node": "pve-1", + "type": "qemu", + "vmid": 100, + "smbios1": "uuid=8af7110d-bfad-407a-a663-9527d10a6583", + }, + }) + }, + ) + + httpmock.RegisterResponder("GET", "https://127.0.0.1:8006/api2/json/nodes/pve-2/qemu/101/config", + func(_ *http.Request) (*http.Response, error) { + return httpmock.NewJsonResponse(200, map[string]interface{}{ + "data": map[string]interface{}{ + "node": "pve-2", + "type": "qemu", + "vmid": 101, + "smbios1": "uuid=5d04cb23-ea78-40a3-af2e-dd54798dc887", + }, + }) + }, + ) + + httpmock.RegisterResponder("GET", "https://127.0.0.2:8006/api2/json/nodes/pve-3/qemu/100/config", + func(_ *http.Request) (*http.Response, error) { + return httpmock.NewJsonResponse(200, map[string]interface{}{ + "data": map[string]interface{}{ + "node": "pve-3", + "type": "qemu", + "vmid": 100, + "smbios1": "uuid=3d3db687-89dd-473e-8463-6599f25b36a8", + }, + }) + }, + ) + httpmock.RegisterResponder("GET", "https://127.0.0.1:8006/api2/json/nodes/pve-1/qemu/100/status/current", func(_ *http.Request) (*http.Response, error) { return httpmock.NewJsonResponse(200, map[string]interface{}{ @@ -207,9 +249,30 @@ func (ts *ccmTestSuite) TestInstanceExists() { Spec: v1.NodeSpec{ ProviderID: "proxmox://cluster-1/100", }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + SystemUUID: "8af7110d-bfad-407a-a663-9527d10a6583", + }, + }, }, - expectedError: "vm.name(cluster-1-node-1) != node.name(cluster-1-node-3)", - expected: false, + expected: true, + }, + { + 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, }, } @@ -288,6 +351,11 @@ func (ts *ccmTestSuite) TestInstanceShutdown() { Spec: v1.NodeSpec{ ProviderID: "proxmox://cluster-1/100", }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + SystemUUID: "8af7110d-bfad-407a-a663-9527d10a6583", + }, + }, }, expected: false, }, @@ -303,6 +371,40 @@ func (ts *ccmTestSuite) TestInstanceShutdown() { }, expected: true, }, + { + msg: "NodeExistsWithDifferentName", + 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-bfad-407a-a663-9527d10a6583", + }, + }, + }, + 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, + }, } for _, testCase := range tests { @@ -398,6 +500,11 @@ func (ts *ccmTestSuite) TestInstanceMetadata() { cloudproviderapi.AnnotationAlphaProvidedIPAddr: "1.2.3.4", }, }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + SystemUUID: "8af7110d-bfad-407a-a663-9527d10a6583", + }, + }, }, expected: &cloudprovider.InstanceMetadata{ ProviderID: "proxmox://cluster-1/100", @@ -425,6 +532,11 @@ func (ts *ccmTestSuite) TestInstanceMetadata() { cloudproviderapi.AnnotationAlphaProvidedIPAddr: "1.2.3.4,2001::1", }, }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + SystemUUID: "8af7110d-bfad-407a-a663-9527d10a6583", + }, + }, }, expected: &cloudprovider.InstanceMetadata{ ProviderID: "proxmox://cluster-1/100", @@ -456,6 +568,11 @@ func (ts *ccmTestSuite) TestInstanceMetadata() { cloudproviderapi.AnnotationAlphaProvidedIPAddr: "1.2.3.4", }, }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + SystemUUID: "3d3db687-89dd-473e-8463-6599f25b36a8", + }, + }, }, expected: &cloudprovider.InstanceMetadata{ ProviderID: "proxmox://cluster-2/100",