Skip to content

Commit b2e93ff

Browse files
committed
Add capi ipam support
1 parent 0092c53 commit b2e93ff

File tree

2 files changed

+131
-18
lines changed

2 files changed

+131
-18
lines changed

api/v1alpha1/ironcoremetalmachine_types.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ type IroncoreMetalMachineSpec struct {
3131
// This is used to claim specific Server types for a IroncoreMetalMachine.
3232
// +optional
3333
ServerSelector *metav1.LabelSelector `json:"serverSelector,omitempty"`
34+
35+
// IPAMConfig is a list of references to Network resources that should be used to assign IP addresses to the worker nodes.
36+
// +optional
37+
IPAMConfig []IPAMConfig `json:"ipamConfig,omitempty"`
38+
// Metadata is a key-value map of additional data which should be passed to the Machine.
39+
// +optional
40+
Metadata map[string]string `json:"metadata,omitempty"`
3441
}
3542

3643
// IroncoreMetalMachineStatus defines the observed state of IroncoreMetalMachine
@@ -102,3 +109,21 @@ type IroncoreMetalMachineList struct {
102109
func init() {
103110
SchemeBuilder.Register(&IroncoreMetalMachine{}, &IroncoreMetalMachineList{})
104111
}
112+
113+
// IPAMObjectReference is a reference to the IPAM object, which will be used for IP allocation.
114+
type IPAMObjectReference struct {
115+
// Name is the name of resource being referenced.
116+
Name string `json:"name"`
117+
// APIGroup is the group for the resource being referenced.
118+
APIGroup string `json:"apiGroup"`
119+
// Kind is the type of resource being referenced.
120+
Kind string `json:"kind"`
121+
}
122+
123+
// IPAMConfig is a reference to an IPAM resource.
124+
type IPAMConfig struct {
125+
// MetadataKey is the name of metadata key for the network.
126+
MetadataKey string `json:"metadataKey"`
127+
// IPAMRef is a reference to the IPAM object, which will be used for IP allocation.
128+
IPAMRef *IPAMObjectReference `json:"ipamRef"`
129+
}

internal/controller/ironcoremetalmachine_controller.go

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,35 @@ package controller
55

66
import (
77
"context"
8+
"encoding/json"
89
"fmt"
910
"strings"
11+
"time"
1012

1113
"github.com/go-logr/logr"
14+
"github.com/imdario/mergo"
15+
infrav1alpha1 "github.com/ironcore-dev/cluster-api-provider-ironcore-metal/api/v1alpha1"
1216
"github.com/ironcore-dev/cluster-api-provider-ironcore-metal/internal/scope"
1317
"github.com/ironcore-dev/controller-utils/clientutils"
18+
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
1419
"github.com/pkg/errors"
1520
corev1 "k8s.io/api/core/v1"
1621
apierrors "k8s.io/apimachinery/pkg/api/errors"
1722
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/runtime"
1824
"k8s.io/apimachinery/pkg/types"
25+
"k8s.io/apimachinery/pkg/util/wait"
1926
"k8s.io/klog/v2"
20-
2127
clusterapiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
28+
capiv1beta1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
2229
"sigs.k8s.io/cluster-api/util"
2330
"sigs.k8s.io/cluster-api/util/annotations"
24-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
25-
"sigs.k8s.io/controller-runtime/pkg/handler"
26-
"sigs.k8s.io/controller-runtime/pkg/reconcile"
27-
28-
"k8s.io/apimachinery/pkg/runtime"
2931
ctrl "sigs.k8s.io/controller-runtime"
3032
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
34+
"sigs.k8s.io/controller-runtime/pkg/handler"
3135
"sigs.k8s.io/controller-runtime/pkg/log"
32-
33-
infrav1alpha1 "github.com/ironcore-dev/cluster-api-provider-ironcore-metal/api/v1alpha1"
34-
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
36+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3537
)
3638

3739
// IroncoreMetalMachineReconciler reconciles a IroncoreMetalMachine object
@@ -220,8 +222,15 @@ func (r *IroncoreMetalMachineReconciler) reconcileNormal(ctx context.Context, ma
220222
return ctrl.Result{}, err
221223
}
222224

225+
machineScope.Info("Creating secret data", "Secret", machineScope.IroncoreMetalMachine.Name)
226+
secretData, err := r.createSecretData(ctx, machineScope.Logger, machineScope.IroncoreMetalMachine, bootstrapSecret.Data["value"])
227+
if err != nil {
228+
machineScope.Error(err, "failed to create or patch ignition secret")
229+
return ctrl.Result{}, err
230+
}
231+
223232
machineScope.Info("Creating IgnitionSecret", "Secret", machineScope.IroncoreMetalMachine.Name)
224-
ignitionSecret, err := r.applyIgnitionSecret(ctx, machineScope.Logger, machineScope.IroncoreMetalMachine, bootstrapSecret)
233+
ignitionSecret, err := r.applyIgnitionSecret(ctx, machineScope.Logger, machineScope.IroncoreMetalMachine, bootstrapSecret, secretData)
225234
if err != nil {
226235
machineScope.Error(err, "failed to create or patch ignition secret")
227236
return ctrl.Result{}, err
@@ -254,10 +263,91 @@ func (r *IroncoreMetalMachineReconciler) reconcileNormal(ctx context.Context, ma
254263
return reconcile.Result{}, nil
255264
}
256265

257-
func (r *IroncoreMetalMachineReconciler) applyIgnitionSecret(ctx context.Context, log *logr.Logger, ironcoremetalmachine *infrav1alpha1.IroncoreMetalMachine, capidatasecret *corev1.Secret) (*corev1.Secret, error) {
258-
dataSecret := capidatasecret
259-
findAndReplaceIgnition(ironcoremetalmachine, dataSecret)
266+
func (r *IroncoreMetalMachineReconciler) createSecretData(ctx context.Context, log *logr.Logger, ironcoremetalmachine *infrav1alpha1.IroncoreMetalMachine, secretData []byte) ([]byte, error) {
267+
secretData = findAndReplaceIgnition(ironcoremetalmachine, secretData)
260268

269+
secretDataMap := make(map[string]any)
270+
if err := json.Unmarshal(secretData, &secretDataMap); err != nil {
271+
return nil, fmt.Errorf("failed to unmarshal secret data: %w", err)
272+
}
273+
if err := r.applyIPAddresses(ctx, log, ironcoremetalmachine, secretDataMap); err != nil {
274+
return nil, fmt.Errorf("failed to apply IPAddresses: %w", err)
275+
}
276+
return json.Marshal(secretDataMap)
277+
}
278+
279+
func (r *IroncoreMetalMachineReconciler) applyIPAddresses(ctx context.Context, log *logr.Logger, ironcoremetalmachine *infrav1alpha1.IroncoreMetalMachine, data map[string]any) error {
280+
for _, networkRef := range ironcoremetalmachine.Spec.IPAMConfig {
281+
ipAddrKey := client.ObjectKeyFromObject(ironcoremetalmachine)
282+
ipClaim := &capiv1beta1.IPAddressClaim{}
283+
if err := r.Client.Get(ctx, ipAddrKey, ipClaim); err != nil && !apierrors.IsNotFound(err) {
284+
return err
285+
286+
} else if err == nil {
287+
log.V(3).Info("IP found", "IP", ipAddrKey.String())
288+
if ipClaim.Status.AddressRef.Name == "" {
289+
return errors.New("IP address claim isn't ready")
290+
}
291+
292+
} else if apierrors.IsNotFound(err) {
293+
if networkRef.IPAMRef == nil {
294+
return errors.New("ipamRef of an ipamConfig is not set")
295+
}
296+
log.V(3).Info("creating IP address claim", "name", ipAddrKey.String())
297+
apiGroup := capiv1beta1.GroupVersion.Group
298+
ipClaim = &capiv1beta1.IPAddressClaim{
299+
ObjectMeta: metav1.ObjectMeta{
300+
Name: ipAddrKey.Name,
301+
Namespace: ipAddrKey.Namespace,
302+
},
303+
Spec: capiv1beta1.IPAddressClaimSpec{
304+
PoolRef: corev1.TypedLocalObjectReference{
305+
APIGroup: &apiGroup,
306+
Kind: "GlobalInClusterIPPool",
307+
Name: networkRef.IPAMRef.Name,
308+
},
309+
},
310+
}
311+
if err = r.Client.Create(ctx, ipClaim); err != nil {
312+
return fmt.Errorf("error creating IP: %w", err)
313+
}
314+
315+
// Wait for the IP address claim to reach the ready state
316+
err = wait.PollUntilContextTimeout(
317+
ctx,
318+
time.Millisecond*50,
319+
time.Millisecond*340,
320+
true,
321+
func(ctx context.Context) (bool, error) {
322+
if err = r.Client.Get(ctx, ipAddrKey, ipClaim); err != nil && !apierrors.IsNotFound(err) {
323+
return false, err
324+
}
325+
return ipClaim.Status.AddressRef.Name != "", nil
326+
})
327+
if err != nil {
328+
return err
329+
}
330+
}
331+
332+
ipAddr := &capiv1beta1.IPAddress{}
333+
if err := r.Client.Get(ctx, ipAddrKey, ipAddr); err != nil {
334+
return err
335+
}
336+
addressMetaData := map[string]any{
337+
networkRef.MetadataKey: map[string]any{
338+
"ip": ipAddr.Spec.Address,
339+
"prefix": ipAddr.Spec.Prefix,
340+
"gateway": ipAddr.Spec.Gateway,
341+
},
342+
}
343+
if err := mergo.Merge(&data, addressMetaData, mergo.WithOverride); err != nil {
344+
return fmt.Errorf("failed to merge addressMetaData: %w", err)
345+
}
346+
}
347+
return nil
348+
}
349+
350+
func (r *IroncoreMetalMachineReconciler) applyIgnitionSecret(ctx context.Context, log *logr.Logger, ironcoremetalmachine *infrav1alpha1.IroncoreMetalMachine, capidatasecret *corev1.Secret, secretData []byte) (*corev1.Secret, error) {
261351
secretObj := &corev1.Secret{
262352
ObjectMeta: metav1.ObjectMeta{
263353
Name: fmt.Sprintf("ignition-%s", capidatasecret.Name),
@@ -268,7 +358,7 @@ func (r *IroncoreMetalMachineReconciler) applyIgnitionSecret(ctx context.Context
268358
APIVersion: corev1.SchemeGroupVersion.String(),
269359
},
270360
Data: map[string][]byte{
271-
DefaultIgnitionSecretKeyName: dataSecret.Data["value"],
361+
DefaultIgnitionSecretKeyName: secretData,
272362
},
273363
}
274364

@@ -345,12 +435,10 @@ func (r *IroncoreMetalMachineReconciler) ensureServerClaimBound(ctx context.Cont
345435
return true, nil
346436
}
347437

348-
func findAndReplaceIgnition(ironcoremetalmachine *infrav1alpha1.IroncoreMetalMachine, capidatasecret *corev1.Secret) {
349-
data := capidatasecret.Data["value"]
350-
438+
func findAndReplaceIgnition(ironcoremetalmachine *infrav1alpha1.IroncoreMetalMachine, data []byte) []byte {
351439
// replace $${METAL_HOSTNAME} with machine name
352440
hostname := "%24%24%7BMETAL_HOSTNAME%7D"
353441
modifiedData := strings.ReplaceAll(string(data), hostname, ironcoremetalmachine.Name)
354442

355-
capidatasecret.Data["value"] = []byte(modifiedData)
443+
return []byte(modifiedData)
356444
}

0 commit comments

Comments
 (0)