Skip to content

Commit d5880a7

Browse files
committed
Add logic to OpenStackMachineTemplate controller
Add logic to OpenStackMachineTemplate controller to populate status with: - capacity related information taken from the flavor and rootVolume of the osmt - operatingSystem info taken from the image of the osmt
1 parent 489ce9c commit d5880a7

File tree

11 files changed

+630
-22
lines changed

11 files changed

+630
-22
lines changed

api/v1beta1/openstackmachinetemplate_types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type NodeInfo struct {
5050
// +kubebuilder:object:root=true
5151
// +kubebuilder:storageversion
5252
// +kubebuilder:resource:path=openstackmachinetemplates,scope=Namespaced,categories=cluster-api,shortName=osmt
53+
// +kubebuilder:subresource:status
5354

5455
// OpenStackMachineTemplate is the Schema for the openstackmachinetemplates API.
5556
type OpenStackMachineTemplate struct {
@@ -72,3 +73,11 @@ type OpenStackMachineTemplateList struct {
7273
func init() {
7374
objectTypes = append(objectTypes, &OpenStackMachineTemplate{}, &OpenStackMachineTemplateList{})
7475
}
76+
77+
// GetIdentifyRef returns the object's namespace and IdentityRef if it has an IdentityRef, or nulls if it does not.
78+
func (r *OpenStackMachineTemplate) GetIdentityRef() (*string, *OpenStackIdentityReference) {
79+
if r.Spec.Template.Spec.IdentityRef != nil {
80+
return &r.Namespace, r.Spec.Template.Spec.IdentityRef
81+
}
82+
return nil, nil
83+
}

config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachinetemplates.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/manager/manager.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ spec:
2424
- "--v=2"
2525
- "--diagnostics-address=127.0.0.1:8080"
2626
- "--insecure-diagnostics=true"
27-
- "--feature-gates=PriorityQueue=${EXP_CAPO_PRIORITY_QUEUE:=false}"
27+
- "--feature-gates=PriorityQueue=${EXP_CAPO_PRIORITY_QUEUE:=false},AutoScaleFromZero=${EXP_CAPO_AUTOSCALE_FROM_ZERO:=false}"
2828
image: controller:latest
2929
imagePullPolicy: Always
3030
name: manager

controllers/openstackmachine_controller.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import (
5151
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/loadbalancer"
5252
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/networking"
5353
"sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
54+
controllers "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/controllers"
5455
capoerrors "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/errors"
5556
)
5657

@@ -122,7 +123,7 @@ func (r *OpenStackMachineReconciler) Reconcile(ctx context.Context, req ctrl.Req
122123
return ctrl.Result{}, nil
123124
}
124125

125-
infraCluster, err := r.getInfraCluster(ctx, cluster, openStackMachine)
126+
infraCluster, err := controllers.GetInfraCluster(ctx, r.Client, cluster, openStackMachine.Namespace)
126127
if err != nil {
127128
return ctrl.Result{}, errors.New("error getting infra provider cluster")
128129
}
@@ -804,15 +805,3 @@ func (r *OpenStackMachineReconciler) requestsForCluster(ctx context.Context, log
804805
}
805806
return result
806807
}
807-
808-
func (r *OpenStackMachineReconciler) getInfraCluster(ctx context.Context, cluster *clusterv1.Cluster, openStackMachine *infrav1.OpenStackMachine) (*infrav1.OpenStackCluster, error) {
809-
openStackCluster := &infrav1.OpenStackCluster{}
810-
openStackClusterName := client.ObjectKey{
811-
Namespace: openStackMachine.Namespace,
812-
Name: cluster.Spec.InfrastructureRef.Name,
813-
}
814-
if err := r.Client.Get(ctx, openStackClusterName, openStackCluster); err != nil {
815-
return nil, err
816-
}
817-
return openStackCluster, nil
818-
}

controllers/openstackmachinetemplate_controller.go

Lines changed: 152 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023 The Kubernetes Authors.
2+
Copyright 2025 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -17,21 +17,34 @@ package controllers
1717

1818
import (
1919
"context"
20+
"errors"
2021

22+
corev1 "k8s.io/api/core/v1"
2123
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/api/resource"
25+
kerrors "k8s.io/apimachinery/pkg/util/errors"
2226
"k8s.io/client-go/tools/record"
27+
"sigs.k8s.io/cluster-api/util"
28+
"sigs.k8s.io/cluster-api/util/annotations"
29+
"sigs.k8s.io/cluster-api/util/patch"
30+
"sigs.k8s.io/cluster-api/util/predicates"
2331
ctrl "sigs.k8s.io/controller-runtime"
2432
"sigs.k8s.io/controller-runtime/pkg/client"
2533
"sigs.k8s.io/controller-runtime/pkg/controller"
2634

27-
"sigs.k8s.io/cluster-api/util/predicates"
28-
2935
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
36+
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/compute"
3037
"sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
38+
controllers "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/controllers"
3139
)
3240

41+
const imagePropertyForOS = "os_type"
42+
43+
// Set here so we can easily mock it in tests.
44+
var newComputeService = compute.NewService
45+
3346
// OpenStackMachineTemplateReconciler reconciles a OpenStackMachineTemplate object.
34-
// it only updates the .status field to allow auto-scaling
47+
// it only updates the .status field to allow auto-scaling.
3548
type OpenStackMachineTemplateReconciler struct {
3649
Client client.Client
3750
Recorder record.EventRecorder
@@ -40,14 +53,14 @@ type OpenStackMachineTemplateReconciler struct {
4053
CaCertificates []byte // PEM encoded ca certificates.
4154
}
4255

43-
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=openstackmachinetemplatess,verbs=get;list;watch;create;
44-
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=openstackmachinetemplatess/status,verbs=get;update;patch
56+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=openstackmachinetemplates,verbs=get;list;watch
57+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=openstackmachinetemplates/status,verbs=get;update;patch
4558

4659
func (r *OpenStackMachineTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, reterr error) {
4760
log := ctrl.LoggerFrom(ctx)
4861

4962
// Fetch the OpenStackMachine instance.
50-
openStackMachineTemplate := &infrav1.OpenStackMachine{}
63+
openStackMachineTemplate := &infrav1.OpenStackMachineTemplate{}
5164
err := r.Client.Get(ctx, req.NamespacedName, openStackMachineTemplate)
5265
if err != nil {
5366
if apierrors.IsNotFound(err) {
@@ -59,9 +72,141 @@ func (r *OpenStackMachineTemplateReconciler) Reconcile(ctx context.Context, req
5972
log = log.WithValues("openStackMachineTemplate", openStackMachineTemplate.Name)
6073
log.V(4).Info("Reconciling openStackMachineTemplate")
6174

75+
// If OSMT is set for deletion, do nothing
76+
if !openStackMachineTemplate.DeletionTimestamp.IsZero() {
77+
log.Info("OpenStackMachineTemplate marked for deletion, skipping reconciliation")
78+
return ctrl.Result{}, nil
79+
}
80+
81+
// Fetch the Cluster.
82+
cluster, err := util.GetClusterFromMetadata(ctx, r.Client, openStackMachineTemplate.ObjectMeta)
83+
if err != nil {
84+
log.Info("openStackMachineTemplate is missing cluster label or cluster does not exist")
85+
return ctrl.Result{}, nil
86+
}
87+
88+
log = log.WithValues("cluster", cluster.Name)
89+
90+
if annotations.IsPaused(cluster, openStackMachineTemplate) {
91+
log.Info("OpenStackMachineTemplate or linked Cluster is marked as paused. Won't reconcile")
92+
return ctrl.Result{}, nil
93+
}
94+
95+
infraCluster, err := controllers.GetInfraCluster(ctx, r.Client, cluster, openStackMachineTemplate.Namespace)
96+
if err != nil {
97+
return ctrl.Result{}, errors.New("error getting infra provider cluster")
98+
}
99+
if infraCluster == nil {
100+
log.Info("OpenStackCluster not ready", "name", cluster.Spec.InfrastructureRef.Name)
101+
return ctrl.Result{}, nil
102+
}
103+
104+
log = log.WithValues("openStackCluster", infraCluster.Name)
105+
106+
clientScope, err := r.ScopeFactory.NewClientScopeFromObject(ctx, r.Client, r.CaCertificates, log, openStackMachineTemplate, infraCluster)
107+
if err != nil {
108+
return ctrl.Result{}, err
109+
}
110+
scope := scope.NewWithLogger(clientScope, log)
111+
112+
// Initialize the patch helper
113+
patchHelper, err := patch.NewHelper(openStackMachineTemplate, r.Client)
114+
if err != nil {
115+
return ctrl.Result{}, err
116+
}
117+
118+
// Always patch the openStackMachine when exiting this function so we can persist any OpenStackMachine changes.
119+
defer func() {
120+
if err := patchHelper.Patch(ctx, openStackMachineTemplate); err != nil {
121+
log.Error(err, "Failed to patch OpenStackMachineTemplate after reconciliation")
122+
result = ctrl.Result{}
123+
reterr = kerrors.NewAggregate([]error{reterr, err})
124+
}
125+
}()
126+
127+
// Handle non-deleted OpenStackMachineTemplates
128+
if err := r.reconcileNormal(ctx, scope, openStackMachineTemplate); err != nil {
129+
return ctrl.Result{}, err
130+
}
131+
log.V(4).Info("Successfully reconciled OpenStackMachineTemplate")
62132
return ctrl.Result{}, nil
63133
}
64134

135+
func (r *OpenStackMachineTemplateReconciler) reconcileNormal(ctx context.Context, scope *scope.WithLogger, openStackMachineTemplate *infrav1.OpenStackMachineTemplate) (reterr error) {
136+
log := scope.Logger()
137+
138+
computeService, err := newComputeService(scope)
139+
if err != nil {
140+
return err
141+
}
142+
143+
flavorID, err := computeService.GetFlavorID(openStackMachineTemplate.Spec.Template.Spec.FlavorID, openStackMachineTemplate.Spec.Template.Spec.Flavor)
144+
if err != nil {
145+
return err
146+
}
147+
148+
flavor, err := computeService.GetFlavor(flavorID)
149+
if err != nil {
150+
return err
151+
}
152+
153+
log.V(4).Info("Retrieved flavor details", "flavorID", flavorID)
154+
155+
if openStackMachineTemplate.Status.Capacity == nil {
156+
log.V(4).Info("Initializing status capacity map")
157+
openStackMachineTemplate.Status.Capacity = corev1.ResourceList{}
158+
}
159+
160+
if flavor.VCPUs > 0 {
161+
openStackMachineTemplate.Status.Capacity[corev1.ResourceCPU] = *resource.NewQuantity(int64(flavor.VCPUs), resource.DecimalSI)
162+
}
163+
164+
if flavor.RAM > 0 {
165+
// flavor.RAM is in MiB -> convert to bytes
166+
ramBytes := int64(flavor.RAM) * 1024 * 1024
167+
openStackMachineTemplate.Status.Capacity[corev1.ResourceMemory] = *resource.NewQuantity(ramBytes, resource.BinarySI)
168+
}
169+
170+
if flavor.Ephemeral > 0 {
171+
// flavor.Ephemeral is in GiB -> convert to bytes
172+
ephemeralBytes := int64(flavor.Ephemeral) * 1024 * 1024 * 1024
173+
openStackMachineTemplate.Status.Capacity[corev1.ResourceEphemeralStorage] = *resource.NewQuantity(ephemeralBytes, resource.BinarySI)
174+
}
175+
176+
// storage depends on whether user boots-from-volume or not
177+
if openStackMachineTemplate.Spec.Template.Spec.RootVolume != nil && openStackMachineTemplate.Spec.Template.Spec.RootVolume.SizeGiB > 0 {
178+
// RootVolume.SizeGib is in GiB -> convert to bytes
179+
storageBytes := int64(openStackMachineTemplate.Spec.Template.Spec.RootVolume.SizeGiB) * 1024 * 1024 * 1024
180+
openStackMachineTemplate.Status.Capacity[corev1.ResourceStorage] = *resource.NewQuantity(storageBytes, resource.BinarySI)
181+
} else if flavor.Disk > 0 {
182+
// flavor.Disk is in GiB -> convert to bytes
183+
storageBytes := int64(flavor.Disk) * 1024 * 1024 * 1024
184+
openStackMachineTemplate.Status.Capacity[corev1.ResourceStorage] = *resource.NewQuantity(storageBytes, resource.BinarySI)
185+
}
186+
187+
imageID, err := computeService.GetImageID(ctx, r.Client, openStackMachineTemplate.Namespace, openStackMachineTemplate.Spec.Template.Spec.Image)
188+
if err != nil {
189+
return err
190+
}
191+
192+
image, err := computeService.GetImageDetails(*imageID)
193+
if err != nil {
194+
return err
195+
}
196+
197+
log.V(4).Info("Retrieved image details", "imageID", imageID)
198+
199+
if image.Properties != nil {
200+
if v, ok := image.Properties[imagePropertyForOS]; ok {
201+
if osType, ok := v.(string); ok {
202+
openStackMachineTemplate.Status.NodeInfo.OperatingSystem = osType
203+
}
204+
}
205+
}
206+
207+
return nil
208+
}
209+
65210
func (r *OpenStackMachineTemplateReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
66211
log := ctrl.LoggerFrom(ctx)
67212

0 commit comments

Comments
 (0)