Skip to content

Change OOB subnet label to key-value pair #251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,15 @@ The OOB plugin leases an IP address to an out-of-band client, based on a subnet

An IP object with a random IP address from the subnet's vacant list is created in IPAM, the IP address is then leased back to the client. Currently, no cleanup-on-release is performed, so clients with stable identifiers are guaranteed to become stable IP addresses.
### Configuration
As for in-band, a kubernetes namespace shall be passed as a parameter. Further, a subnet label list in the form `value:key` shall be passed, it is used for subnet detection.
Providing those in `oob_config.yaml` goes as follows:
As for in-band, a kubernetes namespace shall be passed as a parameter. Further, a subnet label filter shall be passed, it will be used for subnet detection.
Providing the label filter in `oob_config.yaml` goes as follows: (a subnet will match if it contains all the labels from the filter)
```yaml
namespace: oob-ns
subnetLabel: subnet=dhcp
subnetLabels:
- key: dhcp
value: "true"
- key: foo
value: bar
```
### Notes
- supports both IPv4 and IPv6
Expand Down
6 changes: 5 additions & 1 deletion example/oob_config.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
namespace: oob-ns
subnetLabel: subnet=dhcp
subnetLabels:
- key: dhcp
value: "true"
- key: key-2
value: value-2
9 changes: 7 additions & 2 deletions internal/api/oob_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@

package api

type SubnetLabel struct {
Key string `yaml:"key"`
Value string `yaml:"value"`
}

type OOBConfig struct {
Namespace string `yaml:"namespace"`
SubnetLabel string `yaml:"subnetLabel"`
Namespace string `yaml:"namespace"`
SubnetLabels []SubnetLabel `yaml:"subnetLabels"`
}
67 changes: 47 additions & 20 deletions plugins/oob/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"os"
"strings"

"github.com/ironcore-dev/fedhcp/internal/api"

"github.com/ironcore-dev/fedhcp/internal/helper"

"k8s.io/apimachinery/pkg/types"
Expand All @@ -33,15 +35,11 @@ const (
type K8sClient struct {
Client client.Client
Namespace string
oobLabelValue string
oobLabelKey string
SubnetLabels []api.SubnetLabel
EventRecorder record.EventRecorder
}

func NewK8sClient(namespace string, oobLabel string) (*K8sClient, error) {
if !strings.Contains(oobLabel, "=") {
return nil, fmt.Errorf("invalid subnet label: %s, should be 'key=value'", oobLabel)
}
func NewK8sClient(namespace string, oobLabels []api.SubnetLabel) (*K8sClient, error) {

cfg := kubernetes.GetConfig()
cl := kubernetes.GetClient()
Expand All @@ -61,12 +59,10 @@ func NewK8sClient(namespace string, oobLabel string) (*K8sClient, error) {
recorder := broadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: id})
broadcaster.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: corev1Client.Events("")})

labelKev, labelValue := strings.Split(oobLabel, "=")[0], strings.Split(oobLabel, "=")[1]
k8sClient := K8sClient{
Client: cl,
Namespace: namespace,
oobLabelKey: labelKev,
oobLabelValue: labelValue,
SubnetLabels: oobLabels,
EventRecorder: recorder,
}

Expand Down Expand Up @@ -114,7 +110,7 @@ func (k K8sClient) getIp(
} else {
log.Debugf("Reserved IP %s (%s) already exists in subnet %s", ipamIP.Status.Reserved.String(),
client.ObjectKeyFromObject(ipamIP), ipamIP.Spec.Subnet.Name)
if err := k.applySubnetLabel(ctx, ipamIP); err != nil {
if err := k.applySubnetLabels(ctx, ipamIP); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -170,15 +166,21 @@ func (k K8sClient) prepareCreateIpamIP(ctx context.Context, subnetName string, m

func (k K8sClient) doCreateIpamIP(ctx context.Context, subnetName string, macKey string, ipaddr net.IP, exactIP bool) (*ipamv1alpha1.IP, error) {
var err error

labels := map[string]string{
"mac": macKey,
"origin": origin,
}

for _, label := range k.SubnetLabels {
labels[label.Key] = label.Value
}

ipamIP := &ipamv1alpha1.IP{
ObjectMeta: metav1.ObjectMeta{
GenerateName: macKey + "-" + origin + "-",
Namespace: k.Namespace,
Labels: map[string]string{
"mac": macKey,
"origin": origin,
k.oobLabelKey: k.oobLabelValue,
},
Labels: labels,
},
Spec: ipamv1alpha1.IPSpec{
Subnet: corev1.LocalObjectReference{
Expand Down Expand Up @@ -221,10 +223,14 @@ func (k K8sClient) doCreateIpamIP(ctx context.Context, subnetName string, macKey
}

func (k K8sClient) getOOBNetworks(ctx context.Context, subnetType ipamv1alpha1.SubnetAddressType) ([]string, error) {
// Convert slice to map
subnetLabels := make(map[string]string)
for _, label := range k.SubnetLabels {
subnetLabels[label.Key] = label.Value
}

subnetList := &ipamv1alpha1.SubnetList{}
if err := k.Client.List(ctx, subnetList, client.InNamespace(k.Namespace), client.MatchingLabels{
k.oobLabelKey: k.oobLabelValue,
}); err != nil {
if err := k.Client.List(ctx, subnetList, client.InNamespace(k.Namespace), client.MatchingLabels(subnetLabels)); err != nil {
return nil, fmt.Errorf("error listing OOB subnets: %w", err)
}

Expand Down Expand Up @@ -255,9 +261,30 @@ func (k K8sClient) getMatchingSubnet(ctx context.Context, subnetName string, ipa
return subnet, nil
}

func (k K8sClient) applySubnetLabel(ctx context.Context, ipamIP *ipamv1alpha1.IP) error {
func (k K8sClient) applySubnetLabels(ctx context.Context, ipamIP *ipamv1alpha1.IP) error {
ipamIPBase := ipamIP.DeepCopy()
ipamIP.Labels[k.oobLabelKey] = k.oobLabelValue

currentLabels := ipamIP.Labels
if currentLabels == nil {
currentLabels = make(map[string]string)
}

changed := false
for _, label := range k.SubnetLabels {
if currentVal, exists := currentLabels[label.Key]; !exists || currentVal != label.Value {
log.Debugf("Updating label %s: %s -> %s", label.Key, currentVal, label.Value)
currentLabels[label.Key] = label.Value
changed = true
} else {
log.Debugf("Label %s already set to %s, skipping", label.Key, label.Value)
}
}

if !changed {
log.Debugf("No labels to update for IP %s, skipping patch", client.ObjectKeyFromObject(ipamIP))
return nil
}

if err := k.Client.Patch(ctx, ipamIP, client.MergeFrom(ipamIPBase)); err != nil {
return fmt.Errorf("failed to patch IP %s: %w", client.ObjectKeyFromObject(ipamIP), err)
}
Expand Down
8 changes: 2 additions & 6 deletions plugins/oob/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ func loadConfig(args ...string) (*api.OOBConfig, error) {
return nil, fmt.Errorf("failed to parse config file: %v", err)
}

// TODO remove after https://github.com/ironcore-dev/FeDHCP/issues/221 is implemented
if !strings.Contains(config.SubnetLabel, "=") {
return nil, fmt.Errorf("invalid subnet label: %s, should be 'key=value'", config.SubnetLabel)
}
return config, nil
}

Expand All @@ -81,7 +77,7 @@ func setup6(args ...string) (handler.Handler6, error) {
return nil, err
}

k8sClient, err = NewK8sClient(oobConfig.Namespace, oobConfig.SubnetLabel)
k8sClient, err = NewK8sClient(oobConfig.Namespace, oobConfig.SubnetLabels)
if err != nil {
return nil, fmt.Errorf("failed to create k8s client: %w", err)
}
Expand Down Expand Up @@ -164,7 +160,7 @@ func setup4(args ...string) (handler.Handler4, error) {
return nil, err
}

k8sClient, err = NewK8sClient(oobConfig.Namespace, oobConfig.SubnetLabel)
k8sClient, err = NewK8sClient(oobConfig.Namespace, oobConfig.SubnetLabels)
if err != nil {
return nil, fmt.Errorf("failed to create k8s client: %w", err)
}
Expand Down
Loading