Skip to content
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
5 changes: 5 additions & 0 deletions api/core/v1alpha1/interface_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,11 @@ type Interface struct {
Status InterfaceStatus `json:"status,omitempty,omitzero"`
}

// HasIPv4 reports whether the Interface is configured as a routed IPv4 interface.
func (in *Interface) HasIPv4() bool {
return in.Spec.Switchport == nil && in.Spec.IPv4 != nil
}

// GetConditions implements conditions.Getter.
func (in *Interface) GetConditions() []metav1.Condition {
return in.Status.Conditions
Expand Down
21 changes: 21 additions & 0 deletions api/evpn/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,24 @@ const (
// with the value set to the owning Fabric's name.
FabricLabel = "evpn.networking.metal.ironcore.dev/fabric"
)

// Fabric condition types.
const (
// UnderlayConvergedCondition reports whether all underlay IGP adjacencies are formed.
UnderlayConvergedCondition = "UnderlayConverged"

// OverlayConvergedCondition reports whether all overlay BGP sessions are established.
OverlayConvergedCondition = "OverlayConverged"
)

// Fabric condition reasons.
const (
// ConvergedReason indicates all child resources report Operational=True.
ConvergedReason = "Converged"

// NotConvergedReason indicates one or more child resources are not yet operational.
NotConvergedReason = "NotConverged"

// NoResourcesReason indicates no child resources exist yet for this condition.
NoResourcesReason = "NoResources"
)
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ func main() { //nolint:gocyclo
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorder("fabric-controller"),
Provider: prov,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "Failed to create controller", "controller", "Fabric")
os.Exit(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: spine1-eth1-1
name: leaf1-eth1-1
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: spine2-eth1-1
name: leaf1-eth1-2
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: spine1-eth1-2
name: leaf2-eth1-1
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: spine2-eth1-2
name: leaf2-eth1-2
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: spine1-eth1-3
name: leaf3-eth1-1
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: spine2-eth1-3
name: leaf3-eth1-2
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: leaf1-eth1-1
name: spine1-eth1-1
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: leaf2-eth1-1
name: spine1-eth1-2
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: leaf3-eth1-1
name: spine1-eth1-3
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: leaf1-eth1-2
name: spine2-eth1-1
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: leaf2-eth1-2
name: spine2-eth1-2
spec:
deviceRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kind: Interface
metadata:
labels:
networking.metal.ironcore.dev/role: fabric
networking.metal.ironcore.dev/interface-neighbor: leaf3-eth1-2
name: spine2-eth1-3
spec:
deviceRef:
Expand Down
26 changes: 24 additions & 2 deletions internal/controller/core/ospf_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ func (r *OSPFReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager)
}),
).
// Watches enqueues OSPFs for updates in referenced Interface resources.
// Only triggers on create, delete and update events when the Configured condition changes.
// Updates trigger when IPv4 availability or the Configured condition changes.
Watches(
&v1alpha1.Interface{},
handler.EnqueueRequestsFromMapFunc(r.interfaceToOSPF),
Expand All @@ -274,7 +274,9 @@ func (r *OSPFReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager)
newInterface := e.ObjectNew.(*v1alpha1.Interface)
oldConfigured := conditions.Get(oldInterface, v1alpha1.ConfiguredCondition)
newConfigured := conditions.Get(newInterface, v1alpha1.ConfiguredCondition)
return ((oldConfigured == nil) != (newConfigured == nil)) || (newConfigured != nil && oldConfigured.Status != newConfigured.Status)
return oldInterface.HasIPv4() != newInterface.HasIPv4() ||
((oldConfigured == nil) != (newConfigured == nil)) ||
(newConfigured != nil && oldConfigured.Status != newConfigured.Status)
},
GenericFunc: func(e event.GenericEvent) bool {
return false
Expand Down Expand Up @@ -327,6 +329,26 @@ func (r *OSPFReconciler) reconcile(ctx context.Context, s *ospfScope) (reterr er
return err
}

if intf.Spec.DeviceRef.Name != s.Device.Name {
conditions.Set(s.OSPF, metav1.Condition{
Type: v1alpha1.ConfiguredCondition,
Status: metav1.ConditionFalse,
Reason: v1alpha1.CrossDeviceReferenceReason,
Message: fmt.Sprintf("interface %q belongs to device %q, not %q", ref.Name, intf.Spec.DeviceRef.Name, s.Device.Name),
})
return reconcile.TerminalError(fmt.Errorf("interface %q belongs to a different device", ref.Name))
}

if !intf.HasIPv4() {
conditions.Set(s.OSPF, metav1.Condition{
Type: v1alpha1.ConfiguredCondition,
Status: metav1.ConditionFalse,
Reason: v1alpha1.IPAddressingNotFoundReason,
Message: fmt.Sprintf("interface %q has no IPv4 configuration", ref.Name),
})
return reconcile.TerminalError(fmt.Errorf("interface %q has no IPv4 configuration", ref.Name))
}
Comment thread
nikatza marked this conversation as resolved.

if !conditions.IsConfigured(intf) {
conditions.Set(s.OSPF, metav1.Condition{
Type: v1alpha1.ReadyCondition,
Expand Down
84 changes: 84 additions & 0 deletions internal/controller/core/ospf_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package core

import (
"net/netip"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/meta"
Expand All @@ -12,6 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
"github.com/ironcore-dev/network-operator/internal/conditions"
)

var _ = Describe("OSPF Controller", func() {
Expand Down Expand Up @@ -118,6 +121,87 @@ var _ = Describe("OSPF Controller", func() {
})
})

Context("When a referenced interface has no IPv4 configuration", func() {
Comment thread
felix-kaestner marked this conversation as resolved.
var (
name string
key client.ObjectKey
)

BeforeEach(func() {
device := &v1alpha1.Device{
ObjectMeta: metav1.ObjectMeta{GenerateName: "test-ospf-l2-intf-", Namespace: metav1.NamespaceDefault},
Spec: v1alpha1.DeviceSpec{Endpoint: v1alpha1.Endpoint{Address: "192.168.10.4:9339"}},
}
Expect(k8sClient.Create(ctx, device)).To(Succeed())
name = device.Name
key = client.ObjectKey{Name: name, Namespace: metav1.NamespaceDefault}

intf := &v1alpha1.Interface{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: metav1.NamespaceDefault},
Spec: v1alpha1.InterfaceSpec{
DeviceRef: v1alpha1.LocalObjectReference{Name: name},
Name: "eth1",
AdminState: v1alpha1.AdminStateUp,
Type: v1alpha1.InterfaceTypePhysical,
},
}
Expect(k8sClient.Create(ctx, intf)).To(Succeed())
Eventually(func(g Gomega) {
current := &v1alpha1.Interface{}
g.Expect(k8sClient.Get(ctx, key, current)).To(Succeed())
g.Expect(conditions.IsConfigured(current)).To(BeTrue())
}).Should(Succeed())

ospf := &v1alpha1.OSPF{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: metav1.NamespaceDefault},
Spec: v1alpha1.OSPFSpec{
DeviceRef: v1alpha1.LocalObjectReference{Name: name},
Instance: "UNDERLAY",
RouterID: "10.0.0.10",
InterfaceRefs: []v1alpha1.OSPFInterface{{
LocalObjectReference: v1alpha1.LocalObjectReference{Name: name},
Area: "0.0.0.0",
}},
},
}
Expect(k8sClient.Create(ctx, ospf)).To(Succeed())
})

AfterEach(func() {
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &v1alpha1.OSPF{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: metav1.NamespaceDefault}}))).To(Succeed())
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &v1alpha1.Interface{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: metav1.NamespaceDefault}}))).To(Succeed())
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &v1alpha1.Device{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: metav1.NamespaceDefault}}))).To(Succeed())
})

It("Should recover after IPv4 configuration is added", func() {
Eventually(func(g Gomega) {
resource := &v1alpha1.OSPF{}
g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
configured := meta.FindStatusCondition(resource.Status.Conditions, v1alpha1.ConfiguredCondition)
g.Expect(configured).NotTo(BeNil())
g.Expect(configured.Status).To(Equal(metav1.ConditionFalse))
g.Expect(configured.Reason).To(Equal(v1alpha1.IPAddressingNotFoundReason))
}).Should(Succeed())

Eventually(func(g Gomega) {
intf := &v1alpha1.Interface{}
g.Expect(k8sClient.Get(ctx, key, intf)).To(Succeed())
intf.Spec.IPv4 = &v1alpha1.InterfaceIPv4{
Addresses: []v1alpha1.IPPrefix{{Prefix: netip.MustParsePrefix("10.0.0.1/31")}},
}
g.Expect(k8sClient.Update(ctx, intf)).To(Succeed())
}).Should(Succeed())

Eventually(func(g Gomega) {
resource := &v1alpha1.OSPF{}
g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
configured := meta.FindStatusCondition(resource.Status.Conditions, v1alpha1.ConfiguredCondition)
g.Expect(configured).NotTo(BeNil())
g.Expect(configured.Status).To(Equal(metav1.ConditionTrue))
}).Should(Succeed())
})
})

Context("When an interfaceRef does not exist", func() {
var (
name string
Expand Down
Loading
Loading