feat: enable support for capmox

This makes ccm compatible with cluster api and cluster api provider proxmox (capmox)

Signed-off-by: Matthias Teich <matthias.teich@gdata.de>
This commit is contained in:
Matthias Teich
2025-01-02 09:06:39 +01:00
committed by Serge
parent 63eef87a87
commit 956a30a463
8 changed files with 88 additions and 12 deletions

View File

@@ -16,7 +16,7 @@ maintainers:
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.2.9 version: 0.2.10
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.

View File

@@ -40,6 +40,9 @@ existingConfigSecretKey: config.yaml
# -- Proxmox cluster config. # -- Proxmox cluster config.
config: config:
features:
# specify provider: proxmox if you are using capmox (cluster api provider for proxmox)
provider: 'default'
clusters: [] clusters: []
# - url: https://cluster-api-1.exmple.com:8006/api2/json # - url: https://cluster-api-1.exmple.com:8006/api2/json
# insecure: false # insecure: false

View File

@@ -26,8 +26,20 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
// Provider specifies the provider. Can be 'default' or 'capmox'
type Provider string
// ProviderDefault is the default provider
const ProviderDefault Provider = "default"
// ProviderCapmox is the Provider for capmox
const ProviderCapmox Provider = "capmox"
// ClustersConfig is proxmox multi-cluster cloud config. // ClustersConfig is proxmox multi-cluster cloud config.
type ClustersConfig struct { type ClustersConfig struct {
Features struct {
Provider Provider `yaml:"provider,omitempty"`
} `yaml:"features,omitempty"`
Clusters []struct { Clusters []struct {
URL string `yaml:"url"` URL string `yaml:"url"`
Insecure bool `yaml:"insecure,omitempty"` Insecure bool `yaml:"insecure,omitempty"`
@@ -67,6 +79,10 @@ func ReadCloudConfig(config io.Reader) (ClustersConfig, error) {
} }
} }
if cfg.Features.Provider == "" {
cfg.Features.Provider = ProviderDefault
}
return cfg, nil return cfg, nil
} }

View File

@@ -69,7 +69,7 @@ clusters:
assert.NotNil(t, cfg) assert.NotNil(t, cfg)
assert.Equal(t, 1, len(cfg.Clusters)) assert.Equal(t, 1, len(cfg.Clusters))
// Valid config with one cluster (username/password) // Valid config with one cluster (username/password), implicit default provider
cfg, err = cluster.ReadCloudConfig(strings.NewReader(` cfg, err = cluster.ReadCloudConfig(strings.NewReader(`
clusters: clusters:
- url: https://example.com - url: https://example.com
@@ -81,6 +81,39 @@ clusters:
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, cfg) assert.NotNil(t, cfg)
assert.Equal(t, 1, len(cfg.Clusters)) assert.Equal(t, 1, len(cfg.Clusters))
assert.Equal(t, cluster.ProviderDefault, cfg.Features.Provider)
// Valid config with one cluster (username/password), explicit provider default
cfg, err = cluster.ReadCloudConfig(strings.NewReader(`
features:
provider: 'default'
clusters:
- url: https://example.com
insecure: false
username: "user@pam"
password: "secret"
region: cluster-1
`))
assert.Nil(t, err)
assert.NotNil(t, cfg)
assert.Equal(t, 1, len(cfg.Clusters))
assert.Equal(t, cluster.ProviderDefault, cfg.Features.Provider)
// Valid config with one cluster (username/password), explicit provider capmox
cfg, err = cluster.ReadCloudConfig(strings.NewReader(`
features:
provider: 'capmox'
clusters:
- url: https://example.com
insecure: false
username: "user@pam"
password: "secret"
region: cluster-1
`))
assert.Nil(t, err)
assert.NotNil(t, cfg)
assert.Equal(t, 1, len(cfg.Clusters))
assert.Equal(t, cluster.ProviderCapmox, cfg.Features.Provider)
} }
func TestReadCloudConfigFromFile(t *testing.T) { func TestReadCloudConfigFromFile(t *testing.T) {

View File

@@ -38,6 +38,11 @@ func GetProviderID(region string, vmr *pxapi.VmRef) string {
return fmt.Sprintf("%s://%s/%d", ProviderName, region, vmr.VmId()) return fmt.Sprintf("%s://%s/%d", ProviderName, region, vmr.VmId())
} }
// GetProviderIDFromUUID returns the magic providerID for kubernetes node.
func GetProviderIDFromUUID(uuid string) string {
return fmt.Sprintf("%s://%s", ProviderName, uuid)
}
// GetVMID returns the VM ID from the providerID. // GetVMID returns the VM ID from the providerID.
func GetVMID(providerID string) (int, error) { func GetVMID(providerID string) (int, error) {
if !strings.HasPrefix(providerID, ProviderName) { if !strings.HasPrefix(providerID, ProviderName) {

View File

@@ -65,7 +65,7 @@ func newCloud(config *cluster.ClustersConfig) (cloudprovider.Interface, error) {
return nil, err return nil, err
} }
instancesInterface := newInstances(client) instancesInterface := newInstances(client, config.Features.Provider)
return &cloud{ return &cloud{
client: client, client: client,

View File

@@ -34,12 +34,14 @@ import (
) )
type instances struct { type instances struct {
c *cluster.Cluster c *cluster.Cluster
provider cluster.Provider
} }
func newInstances(client *cluster.Cluster) *instances { func newInstances(client *cluster.Cluster, provider cluster.Provider) *instances {
return &instances{ return &instances{
c: client, c: client,
provider: provider,
} }
} }
@@ -149,6 +151,12 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr
return nil, fmt.Errorf("instances.InstanceMetadata() - failed to find instance by name/uuid %s: %v, skipped", node.Name, err) return nil, fmt.Errorf("instances.InstanceMetadata() - failed to find instance by name/uuid %s: %v, skipped", node.Name, err)
} }
} }
if i.provider == cluster.ProviderCapmox {
providerID = provider.GetProviderIDFromUUID(uuid)
} else {
providerID = provider.GetProviderID(region, vmRef)
}
} else if !strings.HasPrefix(node.Spec.ProviderID, provider.ProviderName) { } 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) klog.V(4).InfoS("instances.InstanceMetadata() omitting unmanaged node", "node", klog.KObj(node), "providerID", node.Spec.ProviderID)
@@ -178,7 +186,7 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr
} }
return &cloudprovider.InstanceMetadata{ return &cloudprovider.InstanceMetadata{
ProviderID: provider.GetProviderID(region, vmRef), ProviderID: providerID,
NodeAddresses: addresses, NodeAddresses: addresses,
InstanceType: instanceType, InstanceType: instanceType,
Zone: vmRef.Node(), Zone: vmRef.Node(),
@@ -192,6 +200,17 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr
} }
func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) { func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) {
if i.provider == cluster.ProviderCapmox {
uuid := node.Status.NodeInfo.SystemUUID
vmRef, region, err := i.c.FindVMByUUID(uuid)
if err != nil {
return nil, "", fmt.Errorf("instances.getInstance() error: %v", err)
}
return vmRef, region, nil
}
vm, region, err := provider.ParseProviderID(node.Spec.ProviderID) vm, region, err := provider.ParseProviderID(node.Spec.ProviderID)
if err != nil { if err != nil {
return nil, "", fmt.Errorf("instances.getInstance() error: %v", err) return nil, "", fmt.Errorf("instances.getInstance() error: %v", err)

View File

@@ -28,8 +28,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster" proxmoxcluster "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
provider "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/provider" "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/provider"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -44,7 +44,7 @@ type ccmTestSuite struct {
} }
func (ts *ccmTestSuite) SetupTest() { func (ts *ccmTestSuite) SetupTest() {
cfg, err := cluster.ReadCloudConfig(strings.NewReader(` cfg, err := proxmoxcluster.ReadCloudConfig(strings.NewReader(`
clusters: clusters:
- url: https://127.0.0.1:8006/api2/json - url: https://127.0.0.1:8006/api2/json
insecure: false insecure: false
@@ -123,12 +123,12 @@ clusters:
}, },
) )
cluster, err := cluster.NewCluster(&cfg, &http.Client{}) cluster, err := proxmoxcluster.NewCluster(&cfg, &http.Client{})
if err != nil { if err != nil {
ts.T().Fatalf("failed to create cluster client: %v", err) ts.T().Fatalf("failed to create cluster client: %v", err)
} }
ts.i = newInstances(cluster) ts.i = newInstances(cluster, proxmoxcluster.ProviderDefault)
} }
func (ts *ccmTestSuite) TearDownTest() { func (ts *ccmTestSuite) TearDownTest() {