Skip to content

Commit a1ffbae

Browse files
hardikdratd9876
authored andcommitted
Add Test Framework for BootServer and Other Minor Improvements (#248)
1 parent deacffd commit a1ffbae

File tree

5 files changed

+206
-11
lines changed

5 files changed

+206
-11
lines changed

api/v1alpha1/constants.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
package v1alpha1
55

66
const (
7-
DefaultIgnitionKey = "ignition" // Key for accessing Ignition configuration data within a Kubernetes Secret object.
8-
DefaultIPXEScriptKey = "ipxe-script" // Key for accessing iPXE script data within the iPXE-specific Secret object.
9-
SystemUUIDIndexKey = "spec.systemUUID" // Field to index resources by their system UUID.
10-
SystemIPIndexKey = "spec.systemIPs" // Field to index resources by their system IP addresses.
11-
DefaultFormatKey = "format" // Key for determining the format of the data stored in a Secret, such as fcos or plain-ignition.
12-
FCOSFormat = "fcos" // Specifies the format value used for Fedora CoreOS specific configurations.
7+
DefaultIgnitionKey = "ignition" // Key for accessing Ignition configuration data within a Kubernetes Secret object.
8+
DefaultIPXEScriptKey = "ipxe-script" // Key for accessing iPXE script data within the iPXE-specific Secret object.
9+
SystemUUIDIndexKey = "spec.systemUUID" // Field to index resources by their system UUID.
10+
SystemIPIndexKey = "spec.systemIPs" // Field to index resources by their system IP addresses.
11+
NetworkIdentifierIndexKey = "spec.networkIdentifiers" // Field to index resources by their network identifiers (IP addresses and MAC addresses).
12+
DefaultFormatKey = "format" // Key for determining the format of the data stored in a Secret, such as fcos or plain-ignition.
13+
FCOSFormat = "fcos" // Specifies the format value used for Fedora CoreOS specific configurations.
1314
)

cmd/main.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,12 @@ func main() {
335335
}
336336

337337
setupLog.Info("starting boot-server")
338-
go bootserver.RunBootServer(bootserverAddr, ipxeServiceURL, mgr.GetClient(), serverLog.WithName("bootserver"), *defaultHttpUKIURL)
338+
go func() {
339+
if err := bootserver.RunBootServer(bootserverAddr, ipxeServiceURL, mgr.GetClient(), serverLog.WithName("bootserver"), *defaultHttpUKIURL); err != nil {
340+
setupLog.Error(err, "boot-server exited")
341+
panic(err)
342+
}
343+
}()
339344

340345
setupLog.Info("starting image-proxy-server")
341346
go bootserver.RunImageProxyServer(imageProxyServerAddr, mgr.GetClient(), serverLog.WithName("imageproxyserver"))
@@ -386,7 +391,7 @@ func IndexHTTPBootConfigByNetworkIDs(ctx context.Context, mgr ctrl.Manager) erro
386391
return mgr.GetFieldIndexer().IndexField(
387392
ctx,
388393
&bootv1alpha1.HTTPBootConfig{},
389-
bootv1alpha1.SystemIPIndexKey,
394+
bootv1alpha1.NetworkIdentifierIndexKey,
390395
func(Obj client.Object) []string {
391396
HTTPBootConfig := Obj.(*bootv1alpha1.HTTPBootConfig)
392397
return HTTPBootConfig.Spec.NetworkIdentifiers

server/bootserver.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ var predefinedConditions = map[string]v1.Condition{
5353

5454
var configDriveCache *ConfigDriveCache
5555

56-
func RunBootServer(ipxeServerAddr string, ipxeServiceURL string, k8sClient client.Client, log logr.Logger, defaultUKIURL string) {
56+
func RunBootServer(ipxeServerAddr string, ipxeServiceURL string, k8sClient client.Client, log logr.Logger, defaultUKIURL string) error {
5757
// Initialize config drive cache with 10 minute TTL and 100MB max size
5858
configDriveCache = NewConfigDriveCache(10*time.Minute, 100*1024*1024)
5959

@@ -102,8 +102,10 @@ func RunBootServer(ipxeServerAddr string, ipxeServiceURL string, k8sClient clien
102102
log.Info("Starting boot server", "address", ipxeServerAddr)
103103
if err := http.ListenAndServe(ipxeServerAddr, nil); err != nil {
104104
log.Error(err, "failed to start boot server")
105-
panic(err)
105+
return err
106106
}
107+
108+
return nil
107109
}
108110

109111
func handleIPXE(w http.ResponseWriter, r *http.Request, k8sClient client.Client, log logr.Logger, ipxeServiceURL string) {
@@ -405,7 +407,7 @@ func handleHTTPBoot(w http.ResponseWriter, r *http.Request, k8sClient client.Cli
405407

406408
var httpBootConfigs bootv1alpha1.HTTPBootConfigList
407409
for _, ip := range clientIPs {
408-
if err := k8sClient.List(ctx, &httpBootConfigs, client.MatchingFields{bootv1alpha1.SystemIPIndexKey: ip}); err != nil {
410+
if err := k8sClient.List(ctx, &httpBootConfigs, client.MatchingFields{bootv1alpha1.NetworkIdentifierIndexKey: ip}); err != nil {
409411
log.Info("Failed to list HTTPBootConfig for IP", "IP", ip, "error", err)
410412
continue
411413
}

server/bootserver_suit_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package server
5+
6+
import (
7+
"fmt"
8+
"net/http"
9+
"testing"
10+
11+
"github.com/go-logr/logr"
12+
bootv1alpha1 "github.com/ironcore-dev/boot-operator/api/v1alpha1"
13+
. "github.com/onsi/ginkgo/v2"
14+
. "github.com/onsi/gomega"
15+
corev1 "k8s.io/api/core/v1"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
19+
)
20+
21+
var (
22+
testServerAddr = ":30003"
23+
testServerURL = "http://localhost:30003"
24+
25+
defaultUKIURL = "https://example.com/default.efi"
26+
ipxeServiceURL = "http://localhost:30004"
27+
28+
k8sClient client.Client
29+
)
30+
31+
func TestBootServer(t *testing.T) {
32+
RegisterFailHandler(Fail)
33+
RunSpecs(t, "Boot Server Suite")
34+
}
35+
36+
var _ = BeforeSuite(func() {
37+
scheme := runtime.NewScheme()
38+
Expect(corev1.AddToScheme(scheme)).To(Succeed())
39+
Expect(bootv1alpha1.AddToScheme(scheme)).To(Succeed())
40+
41+
k8sClient = fake.NewClientBuilder().
42+
WithScheme(scheme).
43+
Build()
44+
45+
errCh := make(chan error, 1)
46+
testLog := logr.Discard()
47+
go func() {
48+
defer GinkgoRecover()
49+
errCh <- RunBootServer(testServerAddr, ipxeServiceURL, k8sClient, testLog, defaultUKIURL)
50+
}()
51+
52+
Eventually(func() error {
53+
select {
54+
case err := <-errCh:
55+
if err != nil {
56+
return err
57+
}
58+
return fmt.Errorf("boot server exited unexpectedly without error")
59+
default:
60+
}
61+
62+
resp, err := http.Get(testServerURL + "/httpboot")
63+
if resp != nil {
64+
_ = resp.Body.Close()
65+
}
66+
return err
67+
}, "5s", "200ms").Should(Succeed())
68+
})

server/bootserver_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package server
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"net/http"
10+
11+
"github.com/go-logr/logr"
12+
bootv1alpha1 "github.com/ironcore-dev/boot-operator/api/v1alpha1"
13+
. "github.com/onsi/ginkgo/v2"
14+
. "github.com/onsi/gomega"
15+
corev1 "k8s.io/api/core/v1"
16+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
)
18+
19+
type httpBootResponse struct {
20+
ClientIPs string `json:"ClientIPs"`
21+
UKIURL string `json:"UKIURL"`
22+
SystemUUID string `json:"SystemUUID,omitempty"`
23+
}
24+
25+
var _ = Describe("BootServer", func() {
26+
Context("/httpboot endpoint", func() {
27+
It("delivers default httpboot data when no HTTPBootConfig matches the client IP", func() {
28+
resp, err := http.Get(testServerURL + "/httpboot")
29+
Expect(err).NotTo(HaveOccurred())
30+
defer func() {
31+
_ = resp.Body.Close()
32+
}()
33+
34+
Expect(resp.StatusCode).To(Equal(http.StatusOK))
35+
Expect(resp.Header.Get("Content-Type")).To(Equal("application/json"))
36+
37+
var body httpBootResponse
38+
Expect(json.NewDecoder(resp.Body).Decode(&body)).To(Succeed())
39+
40+
By("returning the default UKI URL")
41+
Expect(body.UKIURL).To(Equal(defaultUKIURL))
42+
43+
By("including the recorded client IPs")
44+
Expect(body.ClientIPs).NotTo(BeEmpty())
45+
46+
By("not setting a SystemUUID in the default case")
47+
Expect(body.SystemUUID).To(SatisfyAny(BeEmpty(), Equal("")))
48+
})
49+
})
50+
51+
It("converts valid Butane YAML to JSON", func() {
52+
butaneYAML := []byte(`
53+
variant: fcos
54+
version: 1.5.0
55+
systemd:
56+
units:
57+
- name: test.service
58+
enabled: true
59+
`)
60+
61+
jsonData, err := renderIgnition(butaneYAML)
62+
Expect(err).ToNot(HaveOccurred())
63+
Expect(jsonData).ToNot(BeEmpty())
64+
Expect(string(jsonData)).To(ContainSubstring(`"systemd"`))
65+
})
66+
67+
It("returns an error for invalid YAML", func() {
68+
bad := []byte("this ::: is not yaml")
69+
_, err := renderIgnition(bad)
70+
Expect(err).To(HaveOccurred())
71+
})
72+
73+
Context("Verify the SetStatusCondition method", func() {
74+
75+
var testLog = logr.Discard()
76+
77+
It("returns an error for unknown condition type", func() {
78+
cfg := &bootv1alpha1.IPXEBootConfig{
79+
ObjectMeta: v1.ObjectMeta{
80+
Name: "unknown-cond",
81+
Namespace: "default",
82+
},
83+
}
84+
Expect(k8sClient.Create(context.Background(), cfg)).To(Succeed())
85+
86+
err := SetStatusCondition(
87+
context.Background(),
88+
k8sClient,
89+
testLog,
90+
cfg,
91+
"DoesNotExist",
92+
)
93+
94+
Expect(err).To(HaveOccurred())
95+
Expect(err.Error()).To(ContainSubstring("condition type DoesNotExist not found"))
96+
})
97+
98+
It("returns an error for unsupported resource types", func() {
99+
secret := &corev1.Secret{
100+
ObjectMeta: v1.ObjectMeta{
101+
Name: "bad-type",
102+
Namespace: "default",
103+
},
104+
}
105+
_ = k8sClient.Create(context.Background(), secret)
106+
107+
err := SetStatusCondition(
108+
context.Background(),
109+
k8sClient,
110+
testLog,
111+
secret,
112+
"IgnitionDataFetched",
113+
)
114+
115+
Expect(err).To(HaveOccurred())
116+
Expect(err.Error()).To(ContainSubstring("unsupported resource type"))
117+
})
118+
})
119+
})

0 commit comments

Comments
 (0)