Skip to content
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

Implement new configuration strategy for IPAM plugin #219

Merged
merged 2 commits into from
Nov 27, 2024
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
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,16 @@ The IPAM plugin acts as a Kubernetes persistence plugin for IronCore's in-band n

The IPAM plugin does not modify DHCP responses to the client, it rather creates (or updates) IP objects in Kubernetes. For each created IP object, the in-band plugin `onmetal` will lease an IP address to the client. Due to the nature of the IronCore's in-band network - `/127` client networks connected to each switch port - the IP object created has and address calculated by a simple "plus one" rule. In such a way each client gets a "plus one" of the switch port address it is connected to.
### Configuration
A kubernetes namespace shall be passed as a string. All IPAM processing (subnet identification, IP object creation/update) are done in that namespace.
Further, as a second parameter, a comma-separated list of subnet names shall be passed. The IPAM plugin will do the subnet creation based on the IP address of the object to be created as well as on the vacant range of the corresponding subnet.
The IPAM configuration consists of two parameters. First, a kubernetes namespace shall be defined. All IPAM processing (subnet identification, IP object creation/update) are done in that namespace.
Further, a list of subnet names shall be passed. The IPAM plugin will do the subnet creation based on the IP address of the object to be created, as well as on the vacant range of the corresponding subnet.
Providing those in `ipam_config.yaml` goes as follows:
```yaml
namespace: ipam-ns
subnets:
- ipam-subnet1
- ipam-subnet2
- some-other-subnet
```
### Notes
- supports only IPv6
- IPv6 relays are mandatory
Expand Down
2 changes: 1 addition & 1 deletion example/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ server6:
# implement HTTPBoot
- httpboot: http://[2001:db8::1]/image.uki
# add leased IPs to ironcore's IPAM
- ipam: ipam-ns ipam-subnet1,ipam-subnet2,some-other-subnet
- ipam: ipam_config.yaml
# lease IPs based on /127 subnets coming from relays running on the switches
- onmetal: onmetal_config.yaml
# announce DNS servers per DHCP
Expand Down
5 changes: 5 additions & 0 deletions example/ipam_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace: ipam-ns
subnets:
- ipam-subnet1
- ipam-subnet2
- some-other-subnet
9 changes: 9 additions & 0 deletions internal/api/ipam_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: MIT

package api

type IPAMConfig struct {
Namespace string `yaml:"namespace"`
Subnets []string `yaml:"subnets"`
}
42 changes: 31 additions & 11 deletions plugins/ipam/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ package ipam

import (
"fmt"
"net"
"strings"

"github.com/coredhcp/coredhcp/handler"
"github.com/coredhcp/coredhcp/logger"
"github.com/coredhcp/coredhcp/plugins"

"net"
"os"

"github.com/insomniacslk/dhcp/dhcpv6"
"github.com/ironcore-dev/fedhcp/internal/api"
"gopkg.in/yaml.v3"

"github.com/mdlayher/netx/eui64"
)
Expand All @@ -27,24 +31,40 @@ var (
k8sClient *K8sClient
)

func parseArgs(args ...string) (string, []string, error) {
if len(args) < 2 {
return "", []string{""}, fmt.Errorf("at least two arguments must be passed to ipam plugin, a namespace "+
"and a comma-separated subnet names list, got %d", len(args))
// args[0] = path to config file
func parseArgs(args ...string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("exactly one argument must be passed to the metal plugin, got %d", len(args))
}
return args[0], nil
}

func loadConfig(args ...string) (*api.IPAMConfig, error) {
path, err := parseArgs(args...)
if err != nil {
return nil, fmt.Errorf("invalid configuration: %v", err)
}

log.Debugf("Reading ipam config file %s", path)
configData, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %v", err)
}

namespace := args[0]
subnetNames := strings.Split(args[1], ",")
return namespace, subnetNames, nil
config := &api.IPAMConfig{}
if err = yaml.Unmarshal(configData, config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %v", err)
}
return config, nil
}

func setup6(args ...string) (handler.Handler6, error) {
namespace, subnetNames, err := parseArgs(args...)
ipamConfig, err := loadConfig(args...)
if err != nil {
return nil, err
}

k8sClient, err = NewK8sClient(namespace, subnetNames)
k8sClient, err = NewK8sClient(ipamConfig.Namespace, ipamConfig.Subnets)
if err != nil {
return nil, fmt.Errorf("failed to create k8s client: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/onmetal/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func loadConfig(args ...string) (*api.OnMetalConfig, error) {
return nil, fmt.Errorf("invalid configuration: %v", err)
}

log.Debugf("Reading metal config file %s", path)
log.Debugf("Reading onmetal config file %s", path)
configData, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %v", err)
Expand Down