Skip to content

Commit 1651737

Browse files
[NX-OS] Implement support for RoutingPolicy on Cisco NX-OS
1 parent e56b821 commit 1651737

File tree

9 files changed

+383
-24
lines changed

9 files changed

+383
-24
lines changed

api/core/v1alpha1/groupversion_info.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,9 @@ const (
134134
// VRFNotFoundReason indicates that a referenced VRF was not found.
135135
VRFNotFoundReason = "VRFNotFound"
136136
)
137+
138+
// Reasons that are specific to [RoutingPolicy] objects.
139+
const (
140+
// PrefixSetNotFoundReason indicates that a referenced PrefixSet was not found.
141+
PrefixSetNotFoundReason = "PrefixSetNotFound"
142+
)

internal/controller/core/routingpolicy_controller.go

Lines changed: 140 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ import (
1414
"k8s.io/apimachinery/pkg/runtime"
1515
kerrors "k8s.io/apimachinery/pkg/util/errors"
1616
"k8s.io/client-go/tools/record"
17+
"k8s.io/klog/v2"
1718
ctrl "sigs.k8s.io/controller-runtime"
19+
"sigs.k8s.io/controller-runtime/pkg/builder"
1820
"sigs.k8s.io/controller-runtime/pkg/client"
1921
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
22+
"sigs.k8s.io/controller-runtime/pkg/event"
23+
"sigs.k8s.io/controller-runtime/pkg/handler"
2024
"sigs.k8s.io/controller-runtime/pkg/predicate"
25+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2126

2227
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
2328
"github.com/ironcore-dev/network-operator/internal/conditions"
@@ -169,8 +174,10 @@ func (r *RoutingPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reques
169174
return ctrl.Result{}, nil
170175
}
171176

177+
var routingPolicyPrefixSetRefKey = ".spec.statements[].conditions.matchPrefixSet.prefixSetRef.name"
178+
172179
// SetupWithManager sets up the controller with the Manager.
173-
func (r *RoutingPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
180+
func (r *RoutingPolicyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
174181
labelSelector := metav1.LabelSelector{}
175182
if r.WatchFilterValue != "" {
176183
labelSelector.MatchLabels = map[string]string{v1alpha1.WatchLabel: r.WatchFilterValue}
@@ -181,10 +188,37 @@ func (r *RoutingPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
181188
return fmt.Errorf("failed to create label selector predicate: %w", err)
182189
}
183190

191+
if err := mgr.GetFieldIndexer().IndexField(ctx, &v1alpha1.RoutingPolicy{}, routingPolicyPrefixSetRefKey, func(obj client.Object) []string {
192+
rp := obj.(*v1alpha1.RoutingPolicy)
193+
var names []string
194+
for _, stmt := range rp.Spec.Statements {
195+
if stmt.Conditions != nil && stmt.Conditions.MatchPrefixSet != nil {
196+
names = append(names, stmt.Conditions.MatchPrefixSet.PrefixSetRef.Name)
197+
}
198+
}
199+
return names
200+
}); err != nil {
201+
return err
202+
}
203+
184204
return ctrl.NewControllerManagedBy(mgr).
185205
For(&v1alpha1.RoutingPolicy{}).
186206
Named("routingpolicy").
187207
WithEventFilter(filter).
208+
// Watches enqueues RoutingPolicies for updates in referenced PrefixSet resources.
209+
// Only triggers on create and delete events since PrefixSet names are immutable.
210+
Watches(
211+
&v1alpha1.PrefixSet{},
212+
handler.EnqueueRequestsFromMapFunc(r.prefixSetToRoutingPolicy),
213+
builder.WithPredicates(predicate.Funcs{
214+
UpdateFunc: func(e event.UpdateEvent) bool {
215+
return false
216+
},
217+
GenericFunc: func(e event.GenericEvent) bool {
218+
return false
219+
},
220+
}),
221+
).
188222
Complete(r)
189223
}
190224

@@ -211,6 +245,11 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic
211245
}
212246
}
213247

248+
statements, err := r.reconcileStatements(ctx, s)
249+
if err != nil {
250+
return err
251+
}
252+
214253
if err := s.Provider.Connect(ctx, s.Connection); err != nil {
215254
return fmt.Errorf("failed to connect to provider: %w", err)
216255
}
@@ -221,8 +260,9 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic
221260
}()
222261

223262
// Ensure the RoutingPolicy is realized on the provider.
224-
err := s.Provider.EnsureRoutingPolicy(ctx, &provider.RoutingPolicyRequest{
225-
RoutingPolicy: s.RoutingPolicy,
263+
err = s.Provider.EnsureRoutingPolicy(ctx, &provider.EnsureRoutingPolicyRequest{
264+
Name: s.RoutingPolicy.Spec.Name,
265+
Statements: statements,
226266
ProviderConfig: s.ProviderConfig,
227267
})
228268

@@ -234,6 +274,66 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic
234274
return err
235275
}
236276

277+
func (r *RoutingPolicyReconciler) reconcileStatements(ctx context.Context, s *routingPolicyScope) ([]provider.PolicyStatement, error) {
278+
statements := make([]provider.PolicyStatement, 0, len(s.RoutingPolicy.Spec.Statements))
279+
280+
for _, stmt := range s.RoutingPolicy.Spec.Statements {
281+
var conditions []provider.PolicyCondition
282+
switch {
283+
case stmt.Conditions != nil && stmt.Conditions.MatchPrefixSet != nil:
284+
prefixSet, err := r.reconcilePrefixSet(ctx, s, stmt.Conditions.MatchPrefixSet)
285+
if err != nil {
286+
return nil, err
287+
}
288+
conditions = append(conditions, provider.MatchPrefixSetCondition{
289+
PrefixSet: prefixSet,
290+
})
291+
}
292+
293+
statements = append(statements, provider.PolicyStatement{
294+
Sequence: stmt.Sequence,
295+
Conditions: conditions,
296+
Actions: stmt.Actions,
297+
})
298+
}
299+
300+
return statements, nil
301+
}
302+
303+
// reconcilePrefixSet ensures that the referenced PrefixSet exists and belongs to the same device as the RoutingPolicy.
304+
func (r *RoutingPolicyReconciler) reconcilePrefixSet(ctx context.Context, s *routingPolicyScope, c *v1alpha1.PrefixSetMatchCondition) (*v1alpha1.PrefixSet, error) {
305+
key := client.ObjectKey{
306+
Name: c.PrefixSetRef.Name,
307+
Namespace: s.RoutingPolicy.Namespace,
308+
}
309+
310+
prefixSet := new(v1alpha1.PrefixSet)
311+
if err := r.Get(ctx, key, prefixSet); err != nil {
312+
if apierrors.IsNotFound(err) {
313+
conditions.Set(s.RoutingPolicy, metav1.Condition{
314+
Type: v1alpha1.ReadyCondition,
315+
Status: metav1.ConditionFalse,
316+
Reason: v1alpha1.PrefixSetNotFoundReason,
317+
Message: fmt.Sprintf("referenced PrefixSet %q not found", key),
318+
})
319+
return nil, reconcile.TerminalError(fmt.Errorf("referenced PrefixSet %q not found", key))
320+
}
321+
return nil, fmt.Errorf("failed to get referenced PrefixSet %q: %w", key, err)
322+
}
323+
324+
if prefixSet.Spec.DeviceRef.Name != s.Device.Name {
325+
conditions.Set(s.RoutingPolicy, metav1.Condition{
326+
Type: v1alpha1.ReadyCondition,
327+
Status: metav1.ConditionFalse,
328+
Reason: v1alpha1.CrossDeviceReferenceReason,
329+
Message: fmt.Sprintf("referenced PrefixSet %q does not belong to device %q", prefixSet.Name, s.Device.Name),
330+
})
331+
return nil, reconcile.TerminalError(fmt.Errorf("referenced PrefixSet %q does not belong to device %q", prefixSet.Name, s.Device.Name))
332+
}
333+
334+
return prefixSet, nil
335+
}
336+
237337
func (r *RoutingPolicyReconciler) finalize(ctx context.Context, s *routingPolicyScope) (reterr error) {
238338
if err := s.Provider.Connect(ctx, s.Connection); err != nil {
239339
return fmt.Errorf("failed to connect to provider: %w", err)
@@ -244,8 +344,42 @@ func (r *RoutingPolicyReconciler) finalize(ctx context.Context, s *routingPolicy
244344
}
245345
}()
246346

247-
return s.Provider.DeleteRoutingPolicy(ctx, &provider.RoutingPolicyRequest{
248-
RoutingPolicy: s.RoutingPolicy,
249-
ProviderConfig: s.ProviderConfig,
347+
return s.Provider.DeleteRoutingPolicy(ctx, &provider.DeleteRoutingPolicyRequest{
348+
Name: s.RoutingPolicy.Spec.Name,
250349
})
251350
}
351+
352+
// prefixSetToRoutingPolicy is a [handler.MapFunc] to be used to enqueue requests for reconciliation
353+
// for RoutingPolicies when their referenced PrefixSet changes.
354+
func (r *RoutingPolicyReconciler) prefixSetToRoutingPolicy(ctx context.Context, obj client.Object) []ctrl.Request {
355+
prefixSet, ok := obj.(*v1alpha1.PrefixSet)
356+
if !ok {
357+
panic(fmt.Sprintf("Expected a PrefixSet but got a %T", obj))
358+
}
359+
360+
log := ctrl.LoggerFrom(ctx, "PrefixSet", klog.KObj(prefixSet))
361+
362+
routingPolicies := new(v1alpha1.RoutingPolicyList)
363+
if err := r.List(ctx, routingPolicies, client.InNamespace(prefixSet.Namespace), client.MatchingFields{routingPolicyPrefixSetRefKey: prefixSet.Spec.Name}); err != nil {
364+
log.Error(err, "Failed to list RoutingPolicies")
365+
return nil
366+
}
367+
368+
requests := []ctrl.Request{}
369+
for _, rp := range routingPolicies.Items {
370+
for _, stmt := range rp.Spec.Statements {
371+
if stmt.Conditions != nil && stmt.Conditions.MatchPrefixSet != nil && stmt.Conditions.MatchPrefixSet.PrefixSetRef.Name == prefixSet.Spec.Name {
372+
log.Info("Enqueuing RoutingPolicy for reconciliation", "RoutingPolicy", klog.KObj(&rp))
373+
requests = append(requests, ctrl.Request{
374+
NamespacedName: client.ObjectKey{
375+
Name: rp.Name,
376+
Namespace: rp.Namespace,
377+
},
378+
})
379+
break
380+
}
381+
}
382+
}
383+
384+
return requests
385+
}

internal/provider/cisco/nxos/evi.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,32 @@ func (b *BDEVI) XPath() string {
3131
return "System/evpn-items/bdevi-items/BDEvi-list[encap=" + b.Encap + "]"
3232
}
3333

34+
func Community(rt string) (string, error) {
35+
s, err := community(rt)
36+
if err != nil {
37+
return "", err
38+
}
39+
return "regular:" + s, nil
40+
}
41+
3442
func RouteDistinguisher(rd string) (string, error) {
35-
s, err := extcommunity(rd)
43+
s, err := community(rd)
3644
if err != nil {
3745
return "", err
3846
}
3947
return "rd:" + s, nil
4048
}
4149

4250
func RouteTarget(rt string) (string, error) {
43-
s, err := extcommunity(rt)
51+
s, err := community(rt)
4452
if err != nil {
4553
return "", err
4654
}
4755
return "route-target:" + s, nil
4856
}
4957

50-
// extcommunity converts a value to an extended community string.
51-
func extcommunity(s string) (string, error) {
58+
// community converts a value to an bgp community attribute.
59+
func community(s string) (string, error) {
5260
if s == "" {
5361
return "unknown:0:0", nil
5462
}

internal/provider/cisco/nxos/provider.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ var (
5252
_ provider.PIMProvider = (*Provider)(nil)
5353
_ provider.SNMPProvider = (*Provider)(nil)
5454
_ provider.PrefixSetProvider = (*Provider)(nil)
55+
_ provider.RoutingPolicyProvider = (*Provider)(nil)
5556
_ provider.SyslogProvider = (*Provider)(nil)
5657
_ provider.UserProvider = (*Provider)(nil)
5758
_ provider.VLANProvider = (*Provider)(nil)
@@ -1613,6 +1614,55 @@ func (p *Provider) DeletePrefixSet(ctx context.Context, req *provider.PrefixSetR
16131614
return p.client.Delete(ctx, s)
16141615
}
16151616

1617+
func (p *Provider) EnsureRoutingPolicy(ctx context.Context, req *provider.EnsureRoutingPolicyRequest) error {
1618+
rm := new(RouteMap)
1619+
rm.Name = req.Name
1620+
for _, stmt := range req.Statements {
1621+
e := new(RouteMapEntry)
1622+
e.Order = stmt.Sequence
1623+
1624+
for _, cond := range stmt.Conditions {
1625+
switch v := cond.(type) {
1626+
case provider.MatchPrefixSetCondition:
1627+
e.SetPrefixSet(v.PrefixSet)
1628+
default:
1629+
return fmt.Errorf("routing policy: unsupported condition type %T", cond)
1630+
}
1631+
}
1632+
1633+
switch stmt.Actions.RouteDisposition {
1634+
case v1alpha1.AcceptRoute:
1635+
e.Action = ActionPermit
1636+
case v1alpha1.RejectRoute:
1637+
e.Action = ActionDeny
1638+
default:
1639+
return fmt.Errorf("routing policy: unsupported action %q", stmt.Actions.RouteDisposition)
1640+
}
1641+
1642+
if stmt.Actions.BgpActions != nil {
1643+
if stmt.Actions.BgpActions.SetCommunity != nil {
1644+
if err := e.SetCommunities(stmt.Actions.BgpActions.SetCommunity.Communities); err != nil {
1645+
return err
1646+
}
1647+
}
1648+
if stmt.Actions.BgpActions.SetExtCommunity != nil {
1649+
if err := e.SetExtCommunities(stmt.Actions.BgpActions.SetExtCommunity.Communities); err != nil {
1650+
return err
1651+
}
1652+
}
1653+
}
1654+
1655+
rm.EntItems.EntryList.Set(e)
1656+
}
1657+
return p.client.Update(ctx, rm)
1658+
}
1659+
1660+
func (p *Provider) DeleteRoutingPolicy(ctx context.Context, req *provider.DeleteRoutingPolicyRequest) error {
1661+
rm := new(RouteMap)
1662+
rm.Name = req.Name
1663+
return p.client.Delete(ctx, rm)
1664+
}
1665+
16161666
func (p *Provider) EnsureUser(ctx context.Context, req *provider.EnsureUserRequest) error {
16171667
u := new(User)
16181668
u.AllowExpired = "no"

0 commit comments

Comments
 (0)