github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/bandwidth/ops.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package bandwidth
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"log/slog"
    10  
    11  	"github.com/cilium/statedb"
    12  	"github.com/cilium/statedb/reconciler"
    13  	"github.com/vishvananda/netlink"
    14  
    15  	"github.com/cilium/cilium/pkg/datapath/tables"
    16  	"github.com/cilium/cilium/pkg/datapath/types"
    17  )
    18  
    19  type ops struct {
    20  	log       *slog.Logger
    21  	isEnabled func() bool
    22  }
    23  
    24  func newOps(log *slog.Logger, mgr types.BandwidthManager) reconciler.Operations[*tables.BandwidthQDisc] {
    25  	return &ops{log, mgr.Enabled}
    26  }
    27  
    28  // Delete implements reconciler.Operations.
    29  func (*ops) Delete(context.Context, statedb.ReadTxn, *tables.BandwidthQDisc) error {
    30  	// We don't restore the original qdisc on delete.
    31  	return nil
    32  }
    33  
    34  // Prune implements reconciler.Operations.
    35  func (*ops) Prune(context.Context, statedb.ReadTxn, statedb.Iterator[*tables.BandwidthQDisc]) error {
    36  	// We don't touch qdiscs of other devices.
    37  	return nil
    38  }
    39  
    40  // Update implements reconciler.Operations.
    41  func (ops *ops) Update(ctx context.Context, txn statedb.ReadTxn, q *tables.BandwidthQDisc) error {
    42  	if !ops.isEnabled() {
    43  		// Probe results show that the system doesn't support BandwidthManager, so
    44  		// bail out.
    45  		return nil
    46  	}
    47  
    48  	link, err := netlink.LinkByIndex(q.LinkIndex)
    49  	if err != nil {
    50  		return fmt.Errorf("LinkByIndex: %w", err)
    51  	}
    52  	device := link.Attrs().Name
    53  
    54  	// Check if the qdiscs are already set up as expected.
    55  	qdiscs, err := netlink.QdiscList(link)
    56  	if err != nil {
    57  		return fmt.Errorf("QdiscList: %w", err)
    58  	}
    59  	updatedQdiscs := 0
    60  	// Update numEgressQdiscs to only include egress qdiscs while iterating
    61  	numEgressQdiscs := len(qdiscs)
    62  	for _, qdisc := range qdiscs {
    63  		switch qdisc.Attrs().Parent {
    64  		// Update egress qdisc count by excluding clsact and ingress qdiscs.
    65  		// clsact and ingress have the same handle.
    66  		case netlink.HANDLE_CLSACT:
    67  			numEgressQdiscs--
    68  		case netlink.HANDLE_ROOT:
    69  			if qdisc.Type() == "mq" {
    70  				updatedQdiscs++
    71  			}
    72  		default:
    73  			if qdisc.Type() == "fq" {
    74  				fq, _ := qdisc.(*netlink.Fq)
    75  
    76  				// If it's "fq" and with our parameters, then assume this was
    77  				// already set up and there wasn't any MQ support.
    78  				ok := fq.Horizon == uint32(q.FqHorizon.Microseconds()) &&
    79  					fq.Buckets == q.FqBuckets
    80  				if ok {
    81  					updatedQdiscs++
    82  				}
    83  			}
    84  
    85  		}
    86  	}
    87  	if updatedQdiscs == numEgressQdiscs {
    88  		return nil
    89  	}
    90  
    91  	// We strictly want to avoid a down/up cycle on the device at
    92  	// runtime, so given we've changed the default qdisc to FQ, we
    93  	// need to reset the root qdisc, and then set up MQ which will
    94  	// automatically get FQ leaf qdiscs (given it's been default).
    95  	qdisc := &netlink.GenericQdisc{
    96  		QdiscAttrs: netlink.QdiscAttrs{
    97  			LinkIndex: link.Attrs().Index,
    98  			Parent:    netlink.HANDLE_ROOT,
    99  		},
   100  		QdiscType: "noqueue",
   101  	}
   102  	if err := netlink.QdiscReplace(qdisc); err != nil {
   103  		return fmt.Errorf("cannot replace root Qdisc to %s on device %s: %w", qdisc.QdiscType, device, err)
   104  	}
   105  	qdisc = &netlink.GenericQdisc{
   106  		QdiscAttrs: netlink.QdiscAttrs{
   107  			LinkIndex: link.Attrs().Index,
   108  			Parent:    netlink.HANDLE_ROOT,
   109  		},
   110  		QdiscType: "mq",
   111  	}
   112  	which := "mq with fq leaves"
   113  	if err := netlink.QdiscReplace(qdisc); err != nil {
   114  		// No MQ support, so just replace to FQ directly.
   115  		fq := &netlink.Fq{
   116  			QdiscAttrs: netlink.QdiscAttrs{
   117  				LinkIndex: link.Attrs().Index,
   118  				Parent:    netlink.HANDLE_ROOT,
   119  			},
   120  			Pacing: 1,
   121  		}
   122  		// At this point there is nothing we can do about
   123  		// it if we fail here, so hard bail out.
   124  		if err = netlink.QdiscReplace(fq); err != nil {
   125  			return fmt.Errorf("cannot replace root Qdisc to %s on device %s: %w", fq.Type(), device, err)
   126  		}
   127  		which = "fq"
   128  	}
   129  	ops.log.Info("Setting qdisc", "qdisc", which, "device", device)
   130  
   131  	// Set the fq parameters
   132  	qdiscs, err = netlink.QdiscList(link)
   133  	if err != nil {
   134  		return fmt.Errorf("QdiscList: %w", err)
   135  	}
   136  	for _, qdisc := range qdiscs {
   137  		if qdisc.Type() == "fq" {
   138  			fq, _ := qdisc.(*netlink.Fq)
   139  			fq.Horizon = uint32(FqDefaultHorizon.Microseconds())
   140  			fq.Buckets = uint32(FqDefaultBuckets)
   141  			if err := netlink.QdiscReplace(qdisc); err != nil {
   142  				return fmt.Errorf("cannot set qdisc attributes: %w", err)
   143  			}
   144  		}
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  var _ reconciler.Operations[*tables.BandwidthQDisc] = &ops{}