Files
Sankofa/crossplane-provider-proxmox/pkg/controller/vmscaleset/controller.go
defiQUG 9daf1fd378 Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- Add comprehensive database migrations (001-024) for schema evolution
- Enhance API schema with expanded type definitions and resolvers
- Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth
- Implement new services: AI optimization, billing, blockchain, compliance, marketplace
- Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage)
- Update Crossplane provider with enhanced VM management capabilities
- Add comprehensive test suite for API endpoints and services
- Update frontend components with improved GraphQL subscriptions and real-time updates
- Enhance security configurations and headers (CSP, CORS, etc.)
- Update documentation and configuration files
- Add new CI/CD workflows and validation scripts
- Implement design system improvements and UI enhancements
2025-12-12 18:01:35 -08:00

231 lines
7.2 KiB
Go

package vmscaleset
import (
"context"
"fmt"
"os"
"time"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
proxmoxv1alpha1 "github.com/sankofa/crossplane-provider-proxmox/apis/v1alpha1"
"github.com/sankofa/crossplane-provider-proxmox/pkg/metrics"
"github.com/sankofa/crossplane-provider-proxmox/pkg/proxmox"
"github.com/sankofa/crossplane-provider-proxmox/pkg/scaling"
)
// ProxmoxVMScaleSetReconciler reconciles a ProxmoxVMScaleSet object
type ProxmoxVMScaleSetReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=proxmox.sankofa.nexus,resources=proxmoxvmscalesets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=proxmox.sankofa.nexus,resources=proxmoxvmscalesets/status,verbs=get;update;patch
// Reconcile is part of the main kubernetes reconciliation loop
func (r *ProxmoxVMScaleSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
var vmss proxmoxv1alpha1.ProxmoxVMScaleSet
if err := r.Get(ctx, req.NamespacedName, &vmss); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Validate ProviderConfigReference
if vmss.Spec.ProviderConfigReference == nil {
return ctrl.Result{}, errors.New("providerConfigRef is required")
}
if vmss.Spec.ProviderConfigReference.Name == "" {
return ctrl.Result{}, errors.New("providerConfigRef.name is required")
}
// Get ProviderConfig
var providerConfig proxmoxv1alpha1.ProviderConfig
if err := r.Get(ctx, client.ObjectKey{Name: vmss.Spec.ProviderConfigReference.Name}, &providerConfig); err != nil {
return ctrl.Result{}, errors.Wrapf(err, "cannot get provider config")
}
// Get credentials from secret (similar to virtualmachine controller)
creds, err := r.getCredentials(ctx, &providerConfig)
if err != nil {
logger.Error(err, "cannot get credentials")
return ctrl.Result{RequeueAfter: 30 * time.Second}, errors.Wrap(err, "cannot get credentials")
}
// Find the site configuration (use first site or from spec if available)
var site *proxmoxv1alpha1.ProxmoxSite
if len(providerConfig.Spec.Sites) > 0 {
site = &providerConfig.Spec.Sites[0]
} else {
return ctrl.Result{}, errors.New("no sites configured in provider config")
}
// Create Proxmox client with proper credentials
proxmoxClient, err := proxmox.NewClient(
site.Endpoint,
creds.Username,
creds.Password,
site.InsecureSkipTLSVerify,
)
if err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot create Proxmox client")
}
// Create metrics collector with Prometheus client
// Get Prometheus endpoint from environment or ProviderConfig
// For now, we'll use a default endpoint - in production this should come from config
prometheusEndpoint := "http://prometheus:9090"
if prometheusURL := os.Getenv("PROMETHEUS_ENDPOINT"); prometheusURL != "" {
prometheusEndpoint = prometheusURL
}
prometheusClient := metrics.NewPrometheusAPIClient(prometheusEndpoint)
metricsCollector := metrics.NewCollector(prometheusClient)
// Create policy engine
policyEngine := scaling.NewPolicyEngine(metricsCollector)
// Create instance manager
instanceManager := scaling.NewInstanceManager(proxmoxClient)
// Evaluate scaling policies
decision, err := policyEngine.Evaluate(ctx, vmss.Spec, vmss.Status, vmss.Status.Instances)
if err != nil {
logger.Error(err, "failed to evaluate scaling policies")
return ctrl.Result{RequeueAfter: 30 * time.Second}, err
}
// Check cooldown period
if vmss.Status.LastScaleTime != nil {
cooldownPeriod := time.Duration(vmss.Spec.CooldownPeriod) * time.Second
timeSinceLastScale := time.Since(vmss.Status.LastScaleTime.Time)
if timeSinceLastScale < cooldownPeriod && decision.Action != "NO_ACTION" {
logger.Info("Cooldown period active, skipping scaling", "timeSinceLastScale", timeSinceLastScale)
return ctrl.Result{RequeueAfter: cooldownPeriod - timeSinceLastScale}, nil
}
}
// Execute scaling decision
if decision.Action != "NO_ACTION" {
logger.Info("Scaling decision", "action", decision.Action, "newReplicas", decision.NewReplicas, "reason", decision.Reason)
// Scale instances
newInstances, err := instanceManager.ScaleTo(
ctx,
vmss.Spec.Template,
vmss.Status.Instances,
decision.NewReplicas,
)
if err != nil {
logger.Error(err, "failed to scale instances")
return ctrl.Result{RequeueAfter: 30 * time.Second}, err
}
// Update status
now := metav1.Now()
vmss.Status.CurrentReplicas = len(newInstances)
vmss.Status.DesiredReplicas = decision.NewReplicas
vmss.Status.Instances = newInstances
vmss.Status.LastScaleTime = &now
// Add scaling event
event := proxmoxv1alpha1.ScalingEvent{
Type: decision.Action,
OldReplicas: len(vmss.Status.Instances),
NewReplicas: decision.NewReplicas,
Reason: decision.Reason,
Timestamp: now,
}
vmss.Status.ScalingEvents = append(vmss.Status.ScalingEvents, event)
// Keep only last 10 events
if len(vmss.Status.ScalingEvents) > 10 {
vmss.Status.ScalingEvents = vmss.Status.ScalingEvents[len(vmss.Status.ScalingEvents)-10:]
}
// Count ready replicas
readyCount := 0
for _, instance := range newInstances {
if instanceManager.HealthCheck(ctx, instance) {
readyCount++
}
}
vmss.Status.ReadyReplicas = readyCount
if err := r.Status().Update(ctx, &vmss); err != nil {
logger.Error(err, "failed to update status")
return ctrl.Result{}, err
}
}
// Requeue for periodic evaluation
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
// getCredentials retrieves credentials from the provider config secret
func (r *ProxmoxVMScaleSetReconciler) getCredentials(ctx context.Context, config *proxmoxv1alpha1.ProviderConfig) (*credentials, error) {
if config.Spec.Credentials.SecretRef == nil {
return nil, fmt.Errorf("no secret reference in provider config")
}
secretRef := config.Spec.Credentials.SecretRef
// Get secret from Kubernetes
secret := &corev1.Secret{}
secretKey := client.ObjectKey{
Namespace: secretRef.Namespace,
Name: secretRef.Name,
}
if err := r.Get(ctx, secretKey, secret); err != nil {
return nil, errors.Wrap(err, "cannot get secret")
}
// Parse credentials from secret
var username, password string
// Try username/password format first
if userData, ok := secret.Data["username"]; ok {
username = string(userData)
}
if passData, ok := secret.Data["password"]; ok {
password = string(passData)
}
// Try token format (for Proxmox API tokens)
if tokenData, ok := secret.Data["token"]; ok {
if userData, ok := secret.Data["tokenid"]; ok {
username = string(userData)
}
password = string(tokenData)
}
if username == "" || password == "" {
return nil, fmt.Errorf("username/password or token missing in secret")
}
return &credentials{
Username: username,
Password: password,
}, nil
}
type credentials struct {
Username string
Password string
}
// SetupWithManager sets up the controller with the Manager.
func (r *ProxmoxVMScaleSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&proxmoxv1alpha1.ProxmoxVMScaleSet{}).
Complete(r)
}