Skip to content

Commit

Permalink
Implement new configuration strategy for pxeboot plugin (#241)
Browse files Browse the repository at this point in the history
* Implement new configuration strategy for PXEBOOT plugin

* Fix tests

* Fix reuse complaince check

* Update readme

* Dynamically create test configuration data

Do not check in test configuration that small, go for dynamic generation
instead

---------

Co-authored-by: kkumar <[email protected]>
Co-authored-by: Damyan Yordanov <[email protected]>
  • Loading branch information
3 people authored Jan 7, 2025
1 parent 5fe5b27 commit fe7caa8
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 48 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,7 @@ subnetLabel: subnet=dhcp
The Metal plugin acts as a connection link between DHCP and the IronCore metal stack. It creates an `EndPoint` object for each machine with leased IP address. Those endpoints are then consumed by the metal operator, who then creates the corresponding `Machine` objects.

### Configuration
The metal configuration consists of an inventory list. Currently, there are two different ways to provide an inventory list: either by specifying a MAC address filter or by providing the inventory list explicitly. If both a static list and a filter are specified in the `metal_config.yaml`, the static list gets a precedence, so the filter will be ignored.

Providing an explicit static inventory list in `metal_config.yaml` goes as follows:
The metal configuration consists of an inventory list. Currently, there are two different ways to provide an inventory list: either by specifying a MAC address filter or by providing the inventory list explicitly. If both a static list and a filter are specified in the `metal_config.yaml`, the static list gets a precedence, so the filter will be ignored. Providing an explicit static inventory list in `metal_config.yaml` goes as follows:
```yaml
hosts:
- name: server-01
Expand Down Expand Up @@ -129,7 +127,13 @@ The PXEBoot plugin implements an (i)PXE network boot.

When configured properly, the PXEBoot plugin will [break the PXE chainloading loop](https://ipxe.org/howto/dhcpd#pxe_chainloading). In such a way legacy PXE clients will be handed out an iPXE environment, whereas iPXE clients (classified based on the user class for [IPv6](https://datatracker.ietf.org/doc/html/rfc8415#section-21.15) and [IPv4](https://www.rfc-editor.org/rfc/rfc3004.html#section-4)) will get the HTTP PXE boot script.
### Configuration
Two parameters shall be passed as strings: an TFTP address to an iPXE environment and an HTTP(s) boot script address. The order matters!
A TFTP address to an iPXE environment and an HTTP(s) boot script address shall be specified. Providing those in the `pxeboot_config.yaml` goes as follows:

````yaml
tftpServer: tftp://[2001:db8::1]/ipxe/x86_64/ipxe
ipxeServer: http://[2001:db8::1]/ipxe/boot6
````

### Notes
- relays are supported for both IPv4 and IPv6
- TFTP server as well as HTTP boot script server must be provided externally
Expand Down
2 changes: 1 addition & 1 deletion config/default/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ data:
- ipam: metal-api-system dhcp
- onmetal:
- dns: 2001:4860:4860::6464 2001:4860:4860::64
- pxeboot: tftp://[2001:db8::1]/ipxe/x86_64/ipxe http://[2001:db8::1]/ipxe/boot6
- pxeboot: pxeboot_config.yaml
- httpboot: bootservice:http://example.com/boot-operator-endpoint:8083
2 changes: 1 addition & 1 deletion example/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ server6:
# announce DNS servers per DHCP
- dns: 2001:4860:4860::6464 2001:4860:4860::64
# implement (i)PXE boot
- pxeboot: tftp://[2001:db8::1]/ipxe/x86_64/ipxe http://[2001:db8::1]/ipxe/boot6
- pxeboot: pxeboot_config.yaml
# create Endpoint objects in kubernetes
- metal: metal_config.yaml
2 changes: 2 additions & 0 deletions example/pxeboot_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tftpServer: tftp://[2001:db8::1]/ipxe/x86_64/ipxe
ipxeServer: http://[2001:db8::1]/ipxe/boot6
9 changes: 9 additions & 0 deletions internal/api/pxeboot_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 PxebootConfig struct {
TFTPServer string `yaml:"tftpServer"`
IPXEServer string `yaml:"ipxeServer"`
}
57 changes: 45 additions & 12 deletions plugins/pxeboot/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@
//
// server6:
// - plugins:
// - pxeboot: tftp://[2001:db8::dead]/pxe-file http://[2001:db8:a::1]/ipxe-file
// - pxeboot: pxeboot_config.yaml

package pxeboot

import (
"fmt"
"net/url"
"os"

"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/iana"
"github.com/ironcore-dev/fedhcp/internal/api"
"gopkg.in/yaml.v2"

"github.com/coredhcp/coredhcp/handler"
"github.com/coredhcp/coredhcp/logger"
Expand All @@ -45,19 +48,48 @@ var (
tftpBootFileOption, tftpServerNameOption, ipxeBootFileOption *dhcpv4.Option
)

func parseArgs(args ...string) (*url.URL, *url.URL, error) {
if len(args) != 2 {
return nil, nil, fmt.Errorf("exactly two arguments must be passed to PXEBOOT plugin, 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 pxeboot plugin, got %d", len(args))
}
return args[0], nil
}

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

tftp, err := url.Parse(args[0])
log.Debugf("Reading pxeboot config file %s", path)
configData, err := os.ReadFile(path)
if err != nil {
return nil, nil, err
return nil, fmt.Errorf("failed to read config file: %v", err)
}

ipxe, err := url.Parse(args[1])
config := &api.PxebootConfig{}
if err = yaml.Unmarshal(configData, config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %v", err)
}

return config, nil
}

func parseConfig(args ...string) (*url.URL, *url.URL, error) {
pxebootConfig, err := loadConfig(args...)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("invalid configuration: %v", err)
}

tftp, err := url.Parse(pxebootConfig.TFTPServer)
if err != nil {
return nil, nil, fmt.Errorf("invalid tftp url: %v", err)
}

ipxe, err := url.Parse(pxebootConfig.IPXEServer)
if err != nil {
return nil, nil, fmt.Errorf("invalid ipxe url: %v", err)
}

if tftp.Scheme != "tftp" || tftp.Host == "" || tftp.Path == "" || tftp.Path[0] != '/' || tftp.Path[1:] == "" {
Expand All @@ -67,13 +99,14 @@ func parseArgs(args ...string) (*url.URL, *url.URL, error) {
if (ipxe.Scheme != "http" && ipxe.Scheme != "https") || ipxe.Host == "" || ipxe.Path == "" {
return nil, nil, fmt.Errorf("malformed iPXE parameter, should be a valid URL")
}

return tftp, ipxe, nil
}

func setup4(args ...string) (handler.Handler4, error) {
tftp, ipxe, err := parseArgs(args...)
tftp, ipxe, err := parseConfig(args...)
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid configuration: %v", err)
}

opt1 := dhcpv4.OptBootFileName(tftp.Path[1:])
Expand Down Expand Up @@ -133,9 +166,9 @@ func pxeBootHandler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
}

func setup6(args ...string) (handler.Handler6, error) {
tftp, ipxe, err := parseArgs(args...)
tftp, ipxe, err := parseConfig(args...)
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid configuration: %v", err)
}

tftpOption = dhcpv6.OptBootFileURL(tftp.String())
Expand Down
Loading

0 comments on commit fe7caa8

Please sign in to comment.