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{}