Files
kubernetes-operator/internal/controller/nbroutingpeer_controller_test.go
Philip Laine ea9f1cb081 Modernize for Go 1.26 (#232)
This change replaces all uses of pointer utils with the new `new`
function which does the same job.

Signed-off-by: Philip Laine <philip.laine@gmail.com>
2026-05-05 13:53:44 +02:00

1169 lines
41 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause
package controller
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"github.com/go-logr/logr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
netbird "github.com/netbirdio/netbird/shared/management/client/rest"
"github.com/netbirdio/netbird/shared/management/http/api"
nbv1 "github.com/netbirdio/kubernetes-operator/api/v1"
)
var _ = Describe("NBRoutingPeer Controller", func() {
Context("When reconciling a resource", func() {
const resourceName = "test-resource"
ctx := context.Background()
typeNamespacedName := types.NamespacedName{
Name: resourceName,
Namespace: "default",
}
nbroutingpeer := &nbv1.NBRoutingPeer{}
var netbirdClient *netbird.Client
var mux *http.ServeMux
var server *httptest.Server
var controllerReconciler *NBRoutingPeerReconciler
BeforeEach(func() {
ctrl.SetLogger(logr.New(GinkgoLogr.GetSink()))
mux = &http.ServeMux{}
server = httptest.NewServer(mux)
netbirdClient = netbird.New(server.URL, "ABC")
controllerReconciler = &NBRoutingPeerReconciler{
Client: k8sClient,
Netbird: netbirdClient,
ClientImage: "netbirdio/netbird:latest",
ClusterName: "kubernetes",
DefaultLabels: make(map[string]string),
NamespacedNetworks: false,
}
By("creating the custom resource for the Kind NBRoutingPeer")
err := k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)
if err != nil && errors.IsNotFound(err) {
nbroutingpeer = &nbv1.NBRoutingPeer{
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: "default",
Finalizers: []string{"netbird.io/cleanup"},
},
Spec: nbv1.NBRoutingPeerSpec{
Replicas: new(int32(0)),
},
}
Expect(k8sClient.Create(ctx, nbroutingpeer)).To(Succeed())
}
})
AfterEach(func() {
resource := &nbv1.NBRoutingPeer{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
if len(resource.Finalizers) > 0 {
resource.Finalizers = nil
Expect(k8sClient.Update(ctx, resource)).To(Succeed())
}
err = k8sClient.Delete(ctx, resource)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
}
group := &nbv1.NBGroup{}
err = k8sClient.Get(ctx, typeNamespacedName, group)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
if len(group.Finalizers) > 0 {
group.Finalizers = nil
Expect(k8sClient.Update(ctx, group)).To(Succeed())
}
err = k8sClient.Delete(ctx, group)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
}
deploy := &appsv1.Deployment{}
err = k8sClient.Get(ctx, typeNamespacedName, deploy)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
if len(deploy.Finalizers) > 0 {
deploy.Finalizers = nil
Expect(k8sClient.Update(ctx, deploy)).To(Succeed())
}
err = k8sClient.Delete(ctx, deploy)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
}
secret := &corev1.Secret{}
err = k8sClient.Get(ctx, typeNamespacedName, secret)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
if len(secret.Finalizers) > 0 {
secret.Finalizers = nil
Expect(k8sClient.Update(ctx, secret)).To(Succeed())
}
err = k8sClient.Delete(ctx, secret)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
}
nbresource := &nbv1.NBResource{}
err = k8sClient.Get(ctx, typeNamespacedName, nbresource)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
if len(nbresource.Finalizers) > 0 {
nbresource.Finalizers = nil
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
}
err = k8sClient.Delete(ctx, nbresource)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
}
})
When("Network doesn't exist", func() {
BeforeEach(func() {
group := &nbv1.NBGroup{
ObjectMeta: metav1.ObjectMeta{
Name: typeNamespacedName.Name,
Namespace: typeNamespacedName.Namespace,
},
Spec: nbv1.NBGroupSpec{
Name: controllerReconciler.ClusterName,
},
}
Expect(k8sClient.Create(ctx, group)).To(Succeed())
})
It("should create network", func() {
networkCreated := false
mux.HandleFunc("/api/networks", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
switch r.Method {
case http.MethodPost:
networkCreated = true
var req api.PostApiNetworksJSONRequestBody
bs, err := io.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
Expect(json.Unmarshal(bs, &req)).To(Succeed())
Expect(req.Name).To(Equal(controllerReconciler.ClusterName))
Expect(req.Description).NotTo(BeNil())
Expect(req.Description).To(BeEquivalentTo(&networkDescription))
resp := api.Network{
Id: "test",
Description: req.Description,
Name: req.Name,
}
bs, err = json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
case http.MethodGet:
_, err := w.Write([]byte("[]"))
Expect(err).NotTo(HaveOccurred())
}
})
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(networkCreated).To(BeTrue())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
Expect(nbroutingpeer.Status.NetworkID).NotTo(BeNil())
Expect(*nbroutingpeer.Status.NetworkID).To(Equal("test"))
})
})
When("Network exists", func() {
BeforeEach(func() {
mux.HandleFunc("/api/networks", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodGet {
resp := []api.Network{
{
Id: "test",
Description: &networkDescription,
Name: controllerReconciler.ClusterName,
},
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
nbroutingpeer.Status.NetworkID = new("test")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
})
Describe("Network Router changes", func() {
BeforeEach(func() {
group := &nbv1.NBGroup{
ObjectMeta: metav1.ObjectMeta{
Name: typeNamespacedName.Name,
Namespace: typeNamespacedName.Namespace,
},
Spec: nbv1.NBGroupSpec{
Name: controllerReconciler.ClusterName,
},
}
Expect(k8sClient.Create(ctx, group)).To(Succeed())
group.Status.GroupID = new("test")
Expect(k8sClient.Status().Update(ctx, group)).To(Succeed())
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
resp := api.SetupKey{
Id: "skid",
Revoked: false,
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
})
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: typeNamespacedName.Namespace,
Name: typeNamespacedName.Name,
},
Data: map[string][]byte{
"setupKey": []byte("SuperSecret"),
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
})
When("Network Router doesn't exist", func() {
It("should create network router", func() {
routerCreated := false
mux.HandleFunc("/api/networks/test/routers", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
switch r.Method {
case http.MethodPost:
routerCreated = true
var req api.PostApiNetworksNetworkIdRoutersJSONRequestBody
bs, err := io.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
Expect(json.Unmarshal(bs, &req)).To(Succeed())
Expect(req.Enabled).To(BeTrue())
Expect(req.Masquerade).To(BeTrue())
Expect(req.Metric).To(Equal(9999))
Expect(req.PeerGroups).NotTo(BeNil())
Expect(*req.PeerGroups).To(ConsistOf([]string{"test"}))
resp := api.NetworkRouter{
Id: "test",
Enabled: true,
Masquerade: true,
Metric: 9999,
PeerGroups: req.PeerGroups,
}
bs, err = json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
case http.MethodGet:
resp := []api.NetworkRouter{}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(routerCreated).To(BeTrue())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
Expect(nbroutingpeer.Status.RouterID).NotTo(BeNil())
Expect(*nbroutingpeer.Status.RouterID).To(Equal("test"))
})
})
When("Network Router is out-of-date", func() {
It("should update network router", func() {
nbroutingpeer.Status.RouterID = new("test")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
routerUpdated := false
mux.HandleFunc("/api/networks/test/routers", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodGet {
resp := []api.NetworkRouter{
{
Id: "test",
Enabled: false,
Masquerade: false,
Metric: 0,
PeerGroups: &[]string{},
},
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
mux.HandleFunc("/api/networks/test/routers/test", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodPut {
routerUpdated = true
var req api.PutApiNetworksNetworkIdRoutersRouterIdJSONRequestBody
bs, err := io.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
Expect(json.Unmarshal(bs, &req)).To(Succeed())
Expect(req.Enabled).To(BeTrue())
Expect(req.Masquerade).To(BeTrue())
Expect(req.Metric).To(Equal(9999))
Expect(req.PeerGroups).NotTo(BeNil())
Expect(*req.PeerGroups).To(ConsistOf([]string{"test"}))
resp := api.NetworkRouter{
Id: "test",
Enabled: true,
Masquerade: true,
Metric: 9999,
PeerGroups: req.PeerGroups,
}
bs, err = json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(routerUpdated).To(BeTrue())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
Expect(nbroutingpeer.Status.RouterID).NotTo(BeNil())
Expect(*nbroutingpeer.Status.RouterID).To(Equal("test"))
})
})
})
When("Network Router exists", func() {
BeforeEach(func() {
nbroutingpeer.Status.RouterID = new("test")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
mux.HandleFunc("/api/networks/test/routers", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodGet {
resp := []api.NetworkRouter{
{
Id: "test",
Enabled: true,
Masquerade: true,
Metric: 9999,
PeerGroups: &[]string{"test"},
},
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
})
When("Group doesn't exist", func() {
BeforeEach(func() {
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
resp := api.SetupKey{
Id: "skid",
Revoked: false,
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
})
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: typeNamespacedName.Namespace,
Name: typeNamespacedName.Name,
},
Data: map[string][]byte{
"setupKey": []byte("SuperSecret"),
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
})
It("should create group and requeue to get its ID", func() {
controllerReconciler.DefaultLabels = map[string]string{"dog": "bark"}
res, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(res.RequeueAfter).To(BeNumerically(">", 0))
group := &nbv1.NBGroup{}
Expect(k8sClient.Get(ctx, typeNamespacedName, group)).To(Succeed())
Expect(group.Spec.Name).To(Equal(controllerReconciler.ClusterName))
Expect(group.Labels).To(HaveKeyWithValue("dog", "bark"))
group.Status.GroupID = new("test")
Expect(k8sClient.Status().Update(ctx, group)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
})
})
When("Group exists", func() {
BeforeEach(func() {
group := &nbv1.NBGroup{
ObjectMeta: metav1.ObjectMeta{
Name: typeNamespacedName.Name,
Namespace: typeNamespacedName.Namespace,
Finalizers: []string{"netbird.io/routing-peer-cleanup", "netbird.io/group-cleanup"},
},
Spec: nbv1.NBGroupSpec{
Name: controllerReconciler.ClusterName,
},
}
Expect(k8sClient.Create(ctx, group)).To(Succeed())
group.Status.GroupID = new("test")
Expect(k8sClient.Status().Update(ctx, group)).To(Succeed())
})
Describe("Setup Key Behavior", func() {
When("Setup key doesn't exist", func() {
It("should create setup key and save it in Secret", func() {
setupKeyCreated := false
mux.HandleFunc("/api/setup-keys", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodPost {
setupKeyCreated = true
var req api.PostApiSetupKeysJSONRequestBody
bs, err := io.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
Expect(json.Unmarshal(bs, &req)).To(Succeed())
Expect(req.AutoGroups).To(ConsistOf([]string{"test"}))
Expect(req.Ephemeral).To(BeEquivalentTo(new(true)))
Expect(req.ExpiresIn).To(BeZero())
Expect(req.Name).To(Equal(controllerReconciler.ClusterName))
Expect(req.Type).To(Equal("reusable"))
Expect(req.UsageLimit).To(BeZero())
resp := api.SetupKeyClear{
AutoGroups: req.AutoGroups,
Ephemeral: *req.Ephemeral,
Id: "test",
Key: "SuperSecretKey",
Name: req.Name,
Valid: true,
}
bs, err = json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(setupKeyCreated).To(BeTrue())
secret := &corev1.Secret{}
Expect(k8sClient.Get(ctx, typeNamespacedName, secret)).To(Succeed())
Expect(secret.Data).To(HaveKey("setupKey"))
Expect(secret.Data["setupKey"]).To(BeEquivalentTo([]byte("SuperSecretKey")))
})
})
When("Setup key exists but secret is invalid", func() {
It("should delete old setup key and requeue to regenerate", func() {
setupKeyCreated := false
mux.HandleFunc("/api/setup-keys", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodPost {
setupKeyCreated = true
var req api.PostApiSetupKeysJSONRequestBody
bs, err := io.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
Expect(json.Unmarshal(bs, &req)).To(Succeed())
Expect(req.AutoGroups).To(ConsistOf([]string{"test"}))
Expect(req.Ephemeral).To(BeEquivalentTo(new(true)))
Expect(req.ExpiresIn).To(BeZero())
Expect(req.Name).To(Equal(controllerReconciler.ClusterName))
Expect(req.Type).To(Equal("reusable"))
Expect(req.UsageLimit).To(BeZero())
resp := api.SetupKeyClear{
AutoGroups: req.AutoGroups,
Ephemeral: *req.Ephemeral,
Id: "test",
Key: "SuperSecretKey",
Name: req.Name,
Valid: true,
}
bs, err = json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
setupKeyDeleted := false
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
switch r.Method {
case http.MethodGet:
resp := api.SetupKey{
Id: "skid",
Revoked: false,
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
case http.MethodDelete:
setupKeyDeleted = true
_, err := w.Write([]byte(`{}`))
Expect(err).NotTo(HaveOccurred())
}
})
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
res, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(res.Requeue).To(BeTrue())
Expect(setupKeyDeleted).To(BeTrue())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(setupKeyCreated).To(BeTrue())
secret := &corev1.Secret{}
Expect(k8sClient.Get(ctx, typeNamespacedName, secret)).To(Succeed())
Expect(secret.Data).To(HaveKey("setupKey"))
Expect(secret.Data["setupKey"]).To(BeEquivalentTo([]byte("SuperSecretKey")))
})
})
When("Setup key is revoked", func() {
It("should delete setup key and requeue to recreate", func() {
setupKeyCreated := false
mux.HandleFunc("/api/setup-keys", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodPost {
setupKeyCreated = true
var req api.PostApiSetupKeysJSONRequestBody
bs, err := io.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
Expect(json.Unmarshal(bs, &req)).To(Succeed())
Expect(req.AutoGroups).To(ConsistOf([]string{"test"}))
Expect(req.Ephemeral).To(BeEquivalentTo(new(true)))
Expect(req.ExpiresIn).To(BeZero())
Expect(req.Name).To(Equal(controllerReconciler.ClusterName))
Expect(req.Type).To(Equal("reusable"))
Expect(req.UsageLimit).To(BeZero())
resp := api.SetupKeyClear{
AutoGroups: req.AutoGroups,
Ephemeral: *req.Ephemeral,
Id: "test",
Key: "SuperSecretKey",
Name: req.Name,
Valid: true,
}
bs, err = json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
setupKeyDeleted := false
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
switch r.Method {
case http.MethodGet:
resp := api.SetupKey{
Id: "skid",
Revoked: true,
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
case http.MethodDelete:
setupKeyDeleted = true
_, err := w.Write([]byte(`{}`))
Expect(err).NotTo(HaveOccurred())
}
})
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: typeNamespacedName.Namespace,
Name: typeNamespacedName.Name,
},
StringData: map[string]string{
"setupKey": "GoneKey",
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
res, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(res.Requeue).To(BeTrue())
Expect(setupKeyDeleted).To(BeTrue())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(setupKeyCreated).To(BeTrue())
secret = &corev1.Secret{}
Expect(k8sClient.Get(ctx, typeNamespacedName, secret)).To(Succeed())
Expect(secret.Data).To(HaveKey("setupKey"))
Expect(secret.Data["setupKey"]).To(BeEquivalentTo([]byte("SuperSecretKey")))
})
})
When("Setup key is deleted", func() {
It("should requeue to recreate", func() {
setupKeyCreated := false
mux.HandleFunc("/api/setup-keys", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
if r.Method == http.MethodPost {
setupKeyCreated = true
var req api.PostApiSetupKeysJSONRequestBody
bs, err := io.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
Expect(json.Unmarshal(bs, &req)).To(Succeed())
Expect(req.AutoGroups).To(ConsistOf([]string{"test"}))
Expect(req.Ephemeral).To(BeEquivalentTo(new(true)))
Expect(req.ExpiresIn).To(BeZero())
Expect(req.Name).To(Equal(controllerReconciler.ClusterName))
Expect(req.Type).To(Equal("reusable"))
Expect(req.UsageLimit).To(BeZero())
resp := api.SetupKeyClear{
AutoGroups: req.AutoGroups,
Ephemeral: *req.Ephemeral,
Id: "test",
Key: "SuperSecretKey",
Name: req.Name,
Valid: true,
}
bs, err = json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
}
})
setupKeyDeleted := false
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
switch r.Method {
case http.MethodGet:
w.WriteHeader(404)
_, err := w.Write([]byte(`{"message": "setup-key skid not found", "code": 404}`))
Expect(err).NotTo(HaveOccurred())
case http.MethodDelete:
setupKeyDeleted = true
_, err := w.Write([]byte(`{}`))
Expect(err).NotTo(HaveOccurred())
}
})
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: typeNamespacedName.Namespace,
Name: typeNamespacedName.Name,
},
StringData: map[string]string{
"setupKey": "GoneKey",
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
res, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(res.Requeue).To(BeTrue())
Expect(setupKeyDeleted).To(BeFalse())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(setupKeyCreated).To(BeTrue())
secret = &corev1.Secret{}
Expect(k8sClient.Get(ctx, typeNamespacedName, secret)).To(Succeed())
Expect(secret.Data).To(HaveKey("setupKey"))
Expect(secret.Data["setupKey"]).To(BeEquivalentTo([]byte("SuperSecretKey")))
})
})
When("Setup key exists and is valid", func() {
It("should do nothing", func() {
setupKeyDeleted := false
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
switch r.Method {
case http.MethodGet:
resp := api.SetupKey{
Id: "skid",
Revoked: false,
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
case http.MethodDelete:
setupKeyDeleted = true
_, err := w.Write([]byte(`{}`))
Expect(err).NotTo(HaveOccurred())
}
})
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: typeNamespacedName.Namespace,
Name: typeNamespacedName.Name,
},
StringData: map[string]string{
"setupKey": "OriginalKey",
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
res, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(res.Requeue).To(BeFalse())
Expect(setupKeyDeleted).To(BeFalse())
secret = &corev1.Secret{}
Expect(k8sClient.Get(ctx, typeNamespacedName, secret)).To(Succeed())
Expect(secret.Data).To(HaveKey("setupKey"))
Expect(secret.Data["setupKey"]).To(BeEquivalentTo([]byte("OriginalKey")))
})
})
})
Describe("Deployment Behavior", func() {
BeforeEach(func() {
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
resp := api.SetupKey{
Id: "skid",
Revoked: false,
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
})
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: typeNamespacedName.Namespace,
Name: typeNamespacedName.Name,
},
Data: map[string][]byte{
"setupKey": []byte("SuperSecret"),
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
})
When("Deployment doesn't exist", func() {
It("should create deployment", func() {
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
Expect(deployment.OwnerReferences).To(HaveLen(1))
Expect(deployment.Spec.Replicas).To(BeEquivalentTo(new(int32(0))))
Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(1))
Expect(deployment.Spec.Template.Spec.Containers[0].Image).To(Equal(controllerReconciler.ClientImage))
})
})
When("Default labels exist", func() {
It("should add labels to Deployment and Pod metadata", func() {
controllerReconciler.DefaultLabels = map[string]string{
"cat": "meow",
"dog": "bark",
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
Expect(deployment.Labels).To(HaveKeyWithValue("cat", "meow"))
Expect(deployment.Labels).To(HaveKeyWithValue("dog", "bark"))
})
})
When("Deployment is out-of-date", func() {
It("should update deployment", func() {
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
deployment.Spec.Replicas = new(int32(15))
Expect(k8sClient.Update(ctx, deployment)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment = &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
Expect(deployment.Spec.Replicas).To(BeEquivalentTo(new(int32(0))))
})
})
When("Deployment is up-to-date", func() {
It("should ", func() {
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
resourceVersion := deployment.ResourceVersion
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment = &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
Expect(deployment.ResourceVersion).To(Equal(resourceVersion))
})
})
When("Privileged mode is enabled", func() {
It("should create deployment with privileged security context", func() {
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
nbroutingpeer.Spec.Privileged = new(true)
Expect(k8sClient.Update(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(1))
container := deployment.Spec.Template.Spec.Containers[0]
Expect(container.SecurityContext).NotTo(BeNil())
Expect(container.SecurityContext.Privileged).NotTo(BeNil())
Expect(*container.SecurityContext.Privileged).To(BeTrue())
Expect(container.SecurityContext.Capabilities).NotTo(BeNil())
Expect(container.SecurityContext.Capabilities.Add).To(ContainElement(corev1.Capability("NET_ADMIN")))
})
})
When("Privileged mode is disabled", func() {
It("should create deployment with non-privileged security context", func() {
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
nbroutingpeer.Spec.Privileged = new(false)
Expect(k8sClient.Update(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(1))
container := deployment.Spec.Template.Spec.Containers[0]
Expect(container.SecurityContext).NotTo(BeNil())
Expect(container.SecurityContext.Privileged).To(BeNil())
Expect(container.SecurityContext.Capabilities).NotTo(BeNil())
Expect(container.SecurityContext.Capabilities.Add).To(ContainElement(corev1.Capability("NET_ADMIN")))
})
})
When("Privileged mode is not specified", func() {
It("should create deployment with default security context (non-privileged)", func() {
// Ensure Privileged is nil (default)
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
nbroutingpeer.Spec.Privileged = nil
Expect(k8sClient.Update(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(1))
container := deployment.Spec.Template.Spec.Containers[0]
Expect(container.SecurityContext).NotTo(BeNil())
Expect(container.SecurityContext.Privileged).To(BeNil())
Expect(container.SecurityContext.Capabilities).NotTo(BeNil())
Expect(container.SecurityContext.Capabilities.Add).To(ContainElement(corev1.Capability("NET_ADMIN")))
})
})
When("Deployment exists and privileged mode changes", func() {
It("should update deployment security context when privileged mode is enabled", func() {
// First create deployment without privileged mode
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
// Verify initial state
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
container := deployment.Spec.Template.Spec.Containers[0]
Expect(container.SecurityContext.Privileged).To(BeNil())
// Enable privileged mode
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
nbroutingpeer.Spec.Privileged = new(true)
Expect(k8sClient.Update(ctx, nbroutingpeer)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
// Verify privileged mode is now enabled
deployment = &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
container = deployment.Spec.Template.Spec.Containers[0]
Expect(container.SecurityContext.Privileged).NotTo(BeNil())
Expect(*container.SecurityContext.Privileged).To(BeTrue())
})
It("should update deployment security context when privileged mode is disabled", func() {
// First create deployment with privileged mode enabled
nbroutingpeer.Spec.Privileged = new(true)
Expect(k8sClient.Update(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
// Verify initial privileged state
deployment := &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
container := deployment.Spec.Template.Spec.Containers[0]
Expect(container.SecurityContext.Privileged).NotTo(BeNil())
Expect(*container.SecurityContext.Privileged).To(BeTrue())
// Disable privileged mode
Expect(k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer)).To(Succeed())
nbroutingpeer.Spec.Privileged = new(false)
Expect(k8sClient.Update(ctx, nbroutingpeer)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
// Verify privileged mode is now disabled
deployment = &appsv1.Deployment{}
Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed())
container = deployment.Spec.Template.Spec.Containers[0]
Expect(container.SecurityContext.Privileged).To(BeNil())
})
})
})
When("NBRoutingPeer is set for deletion", func() {
networkDeleted := false
BeforeEach(func() {
networkDeleted = false
nbroutingpeer.Status.SetupKeyID = new("skid")
Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed())
mux.HandleFunc("/api/setup-keys/skid", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
resp := api.SetupKey{
Id: "skid",
Revoked: false,
}
bs, err := json.Marshal(resp)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(bs)
Expect(err).NotTo(HaveOccurred())
})
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: typeNamespacedName.Namespace,
Name: typeNamespacedName.Name,
},
Data: map[string][]byte{
"setupKey": []byte("SuperSecret"),
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
mux.HandleFunc("/api/networks/test", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
Expect(r.Method).To(Equal(http.MethodDelete))
_, err = w.Write([]byte(`{}`))
Expect(err).NotTo(HaveOccurred())
networkDeleted = true
})
})
It("should remove finalizer from NBGroup", func() {
Expect(k8sClient.Delete(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
group := &nbv1.NBGroup{}
Expect(k8sClient.Get(ctx, typeNamespacedName, group)).To(Succeed())
Expect(group.Finalizers).NotTo(ContainElement("netbird.io/routing-peer-cleanup"))
})
It("should delete Network Router", func() {
Expect(k8sClient.Delete(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(networkDeleted).To(BeTrue())
})
It("should delete Network", func() {
Expect(k8sClient.Delete(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(networkDeleted).To(BeTrue())
})
It("should delete deployment", func() {
Expect(k8sClient.Delete(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
deployment := &appsv1.Deployment{}
err = k8sClient.Get(ctx, typeNamespacedName, deployment)
Expect(errors.IsNotFound(err)).To(BeTrue())
})
It("should delete any hanging NBResources", func() {
nbResource := &nbv1.NBResource{
ObjectMeta: metav1.ObjectMeta{
Name: typeNamespacedName.Name,
Namespace: typeNamespacedName.Namespace,
},
Spec: nbv1.NBResourceSpec{
Name: "test",
NetworkID: *nbroutingpeer.Status.NetworkID,
Address: "test",
Groups: []string{"grp"},
},
}
Expect(k8sClient.Create(ctx, nbResource)).To(Succeed())
Expect(k8sClient.Delete(ctx, nbroutingpeer)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
nbResource = &nbv1.NBResource{}
err = k8sClient.Get(ctx, typeNamespacedName, nbResource)
Expect(errors.IsNotFound(err)).To(BeTrue())
})
})
})
})
})
})
})