From 42eb3c49afdc6ae61affa4ce9c99a236955480d8 Mon Sep 17 00:00:00 2001 From: "Johannes M. Scheuermann" Date: Mon, 20 May 2019 12:52:07 +0200 Subject: [PATCH 1/4] Initial support for traffic shaping Signed-off-by: Johannes M. Scheuermann --- pkg/server/sandbox_run.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pkg/server/sandbox_run.go b/pkg/server/sandbox_run.go index 024bbe401..41d8b890b 100644 --- a/pkg/server/sandbox_run.go +++ b/pkg/server/sandbox_run.go @@ -36,6 +36,7 @@ import ( "golang.org/x/net/context" "golang.org/x/sys/unix" runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2" + "k8s.io/kubernetes/pkg/util/bandwidth" "github.com/containerd/cri/pkg/annotations" criconfig "github.com/containerd/cri/pkg/config" @@ -540,10 +541,21 @@ func (c *criService) setupPod(id string, path string, config *runtime.PodSandbox } labels := getPodCNILabels(id, config) + + // Will return an error if the bandwidth limitation has the wrong unit + // or an unreasonable valure see validateBandwidthIsReasonable() + bandWidth, err := toCNIBandWidth(config.Annotations) + if err != nil { + "", nil, errors.Errorf("failed to find network info for sandbox %q", id) + } + result, err := c.netPlugin.Setup(id, path, cni.WithLabels(labels), - cni.WithCapabilityPortMap(toCNIPortMappings(config.GetPortMappings()))) + cni.WithCapabilityPortMap(toCNIPortMappings(config.GetPortMappings())), + cni.WithCapabilityBandWidth(bandWidth), + ) + if err != nil { return "", nil, err } @@ -559,6 +571,21 @@ func (c *criService) setupPod(id string, path string, config *runtime.PodSandbox return "", result, errors.Errorf("failed to find network info for sandbox %q", id) } +// toCNIPortMappings converts CRI annotations to CNI bandwidth. +func toCNIBandWidth(annotations map[string]string) (cni.BandWidth, error) { + ingress, egress, err := bandwidth.ExtractPodBandwidthResources(annotations) + if err != nil { + return nil, fmt.Errorf("Error reading pod bandwidth annotations: %v", err) + } + + return cni.BandWidth{ + IngressRate: uint64(ingress.Value()), + IngressBurst: 0, + EgressRate: uint64(ingress.Value()), + EgressBurst: 0, + } +} + // toCNIPortMappings converts CRI port mappings to CNI. func toCNIPortMappings(criPortMappings []*runtime.PortMapping) []cni.PortMapping { var portMappings []cni.PortMapping From 0d439c3474f1672e9c5ec4680a4107421eeb4dfe Mon Sep 17 00:00:00 2001 From: "Johannes M. Scheuermann" Date: Fri, 24 May 2019 10:29:52 +0200 Subject: [PATCH 2/4] Implement bandwidth capabilties Signed-off-by: Johannes M. Scheuermann --- pkg/server/sandbox_run.go | 28 +- vendor.conf | 2 +- .../containerd/go-cni/namespace_opts.go | 9 + vendor/github.com/containerd/go-cni/opts.go | 8 +- vendor/github.com/containerd/go-cni/types.go | 8 + .../kubernetes/pkg/util/bandwidth/doc.go | 18 + .../pkg/util/bandwidth/fake_shaper.go | 49 +++ .../pkg/util/bandwidth/interfaces.go | 38 ++ .../kubernetes/pkg/util/bandwidth/linux.go | 339 ++++++++++++++++++ .../pkg/util/bandwidth/unsupported.go | 52 +++ .../kubernetes/pkg/util/bandwidth/utils.go | 65 ++++ 11 files changed, 606 insertions(+), 10 deletions(-) create mode 100644 vendor/k8s.io/kubernetes/pkg/util/bandwidth/doc.go create mode 100644 vendor/k8s.io/kubernetes/pkg/util/bandwidth/fake_shaper.go create mode 100644 vendor/k8s.io/kubernetes/pkg/util/bandwidth/interfaces.go create mode 100644 vendor/k8s.io/kubernetes/pkg/util/bandwidth/linux.go create mode 100644 vendor/k8s.io/kubernetes/pkg/util/bandwidth/unsupported.go create mode 100644 vendor/k8s.io/kubernetes/pkg/util/bandwidth/utils.go diff --git a/pkg/server/sandbox_run.go b/pkg/server/sandbox_run.go index 41d8b890b..bc6bb8dea 100644 --- a/pkg/server/sandbox_run.go +++ b/pkg/server/sandbox_run.go @@ -19,6 +19,7 @@ package server import ( "encoding/json" "fmt" + "math" "os" "strings" @@ -546,14 +547,14 @@ func (c *criService) setupPod(id string, path string, config *runtime.PodSandbox // or an unreasonable valure see validateBandwidthIsReasonable() bandWidth, err := toCNIBandWidth(config.Annotations) if err != nil { - "", nil, errors.Errorf("failed to find network info for sandbox %q", id) + return "", nil, errors.Errorf("failed to find network info for sandbox %q", id) } result, err := c.netPlugin.Setup(id, path, cni.WithLabels(labels), cni.WithCapabilityPortMap(toCNIPortMappings(config.GetPortMappings())), - cni.WithCapabilityBandWidth(bandWidth), + cni.WithCapabilityBandWidth(*bandWidth), ) if err != nil { @@ -572,18 +573,29 @@ func (c *criService) setupPod(id string, path string, config *runtime.PodSandbox } // toCNIPortMappings converts CRI annotations to CNI bandwidth. -func toCNIBandWidth(annotations map[string]string) (cni.BandWidth, error) { +func toCNIBandWidth(annotations map[string]string) (*cni.BandWidth, error) { ingress, egress, err := bandwidth.ExtractPodBandwidthResources(annotations) if err != nil { return nil, fmt.Errorf("Error reading pod bandwidth annotations: %v", err) } - return cni.BandWidth{ - IngressRate: uint64(ingress.Value()), - IngressBurst: 0, - EgressRate: uint64(ingress.Value()), - EgressBurst: 0, + bandWidth := &cni.BandWidth{} + + if ingress == nil && egress == nil { + return bandWidth, nil } + + if ingress != nil { + bandWidth.IngressRate = uint64(ingress.Value()) + bandWidth.IngressBurst = math.MaxUint32 + } + + if egress != nil { + bandWidth.EgressRate = uint64(egress.Value()) + bandWidth.EgressRate = math.MaxUint32 + } + + return bandWidth, nil } // toCNIPortMappings converts CRI port mappings to CNI. diff --git a/vendor.conf b/vendor.conf index 5b04aacba..22446c36c 100644 --- a/vendor.conf +++ b/vendor.conf @@ -5,7 +5,7 @@ github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 github.com/containerd/containerd 32e788a8be3ab4418265693d9e742c30495fdd4c github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4 github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c -github.com/containerd/go-cni 891c2a41e18144b2d7921f971d6c9789a68046b2 +github.com/containerd/go-cni e1dc76fa62e1665cf5d85fd617c6191d66f0e72d github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 github.com/containerd/ttrpc f02858b1457c5ca3aaec3a0803eb0d59f96e41d6 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 diff --git a/vendor/github.com/containerd/go-cni/namespace_opts.go b/vendor/github.com/containerd/go-cni/namespace_opts.go index fad50ebce..e8092e85e 100644 --- a/vendor/github.com/containerd/go-cni/namespace_opts.go +++ b/vendor/github.com/containerd/go-cni/namespace_opts.go @@ -33,6 +33,15 @@ func WithCapabilityIPRanges(ipRanges []IPRanges) NamespaceOpts { } } +// WithCapabilityBandWitdh adds support for traffic shaping: +// https://github.com/heptio/cni-plugins/tree/master/plugins/meta/bandwidth +func WithCapabilityBandWidth(bandWidth BandWidth) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs["bandwidth"] = bandWidth + return nil + } +} + func WithCapability(name string, capability interface{}) NamespaceOpts { return func(c *Namespace) error { c.capabilityArgs[name] = capability diff --git a/vendor/github.com/containerd/go-cni/opts.go b/vendor/github.com/containerd/go-cni/opts.go index c82483617..b277dd37d 100644 --- a/vendor/github.com/containerd/go-cni/opts.go +++ b/vendor/github.com/containerd/go-cni/opts.go @@ -86,6 +86,12 @@ func WithLoNetwork(c *libcni) error { // WithConf can be used to load config directly // from byte. func WithConf(bytes []byte) CNIOpt { + return WithConfIndex(bytes, 0) +} + +// WithConfIndex can be used to load config directly +// from byte and set the interface name's index. +func WithConfIndex(bytes []byte, index int) CNIOpt { return func(c *libcni) error { conf, err := cnilibrary.ConfFromBytes(bytes) if err != nil { @@ -98,7 +104,7 @@ func WithConf(bytes []byte) CNIOpt { c.networks = append(c.networks, &Network{ cni: c.cniConfig, config: confList, - ifName: getIfName(c.prefix, 0), + ifName: getIfName(c.prefix, index), }) return nil } diff --git a/vendor/github.com/containerd/go-cni/types.go b/vendor/github.com/containerd/go-cni/types.go index 1a77fa90a..9a0fe87ff 100644 --- a/vendor/github.com/containerd/go-cni/types.go +++ b/vendor/github.com/containerd/go-cni/types.go @@ -43,3 +43,11 @@ type IPRanges struct { RangeEnd string Gateway string } + +// BandWidth defines the ingress/egress rate and burst limits +type BandWidth struct { + IngressRate uint64 + IngressBurst uint64 + EgressRate uint64 + EgressBurst uint64 +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/bandwidth/doc.go b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/doc.go new file mode 100644 index 000000000..3c26aebbf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package bandwidth provides utilities for bandwidth shaping +package bandwidth // import "k8s.io/kubernetes/pkg/util/bandwidth" diff --git a/vendor/k8s.io/kubernetes/pkg/util/bandwidth/fake_shaper.go b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/fake_shaper.go new file mode 100644 index 000000000..8c95e3bb3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/fake_shaper.go @@ -0,0 +1,49 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bandwidth + +import ( + "errors" + + "k8s.io/apimachinery/pkg/api/resource" +) + +type FakeShaper struct { + CIDRs []string + ResetCIDRs []string +} + +func (f *FakeShaper) Limit(cidr string, egress, ingress *resource.Quantity) error { + return errors.New("unimplemented") +} + +func (f *FakeShaper) Reset(cidr string) error { + f.ResetCIDRs = append(f.ResetCIDRs, cidr) + return nil +} + +func (f *FakeShaper) ReconcileInterface() error { + return errors.New("unimplemented") +} + +func (f *FakeShaper) ReconcileCIDR(cidr string, egress, ingress *resource.Quantity) error { + return errors.New("unimplemented") +} + +func (f *FakeShaper) GetCIDRs() ([]string, error) { + return f.CIDRs, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/bandwidth/interfaces.go b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/interfaces.go new file mode 100644 index 000000000..6b0e160aa --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/interfaces.go @@ -0,0 +1,38 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bandwidth + +import "k8s.io/apimachinery/pkg/api/resource" + +type BandwidthShaper interface { + // Limit the bandwidth for a particular CIDR on a particular interface + // * ingress and egress are in bits/second + // * cidr is expected to be a valid network CIDR (e.g. '1.2.3.4/32' or '10.20.0.1/16') + // 'egress' bandwidth limit applies to all packets on the interface whose source matches 'cidr' + // 'ingress' bandwidth limit applies to all packets on the interface whose destination matches 'cidr' + // Limits are aggregate limits for the CIDR, not per IP address. CIDRs must be unique, but can be overlapping, traffic + // that matches multiple CIDRs counts against all limits. + Limit(cidr string, egress, ingress *resource.Quantity) error + // Remove a bandwidth limit for a particular CIDR on a particular network interface + Reset(cidr string) error + // Reconcile the interface managed by this shaper with the state on the ground. + ReconcileInterface() error + // Reconcile a CIDR managed by this shaper with the state on the ground + ReconcileCIDR(cidr string, egress, ingress *resource.Quantity) error + // GetCIDRs returns the set of CIDRs that are being managed by this shaper + GetCIDRs() ([]string, error) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/bandwidth/linux.go b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/linux.go new file mode 100644 index 000000000..7050b4f76 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/linux.go @@ -0,0 +1,339 @@ +// +build linux + +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bandwidth + +import ( + "bufio" + "bytes" + "encoding/hex" + "fmt" + "net" + "strings" + + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/exec" + + "k8s.io/klog" +) + +// tcShaper provides an implementation of the BandwidthShaper interface on Linux using the 'tc' tool. +// In general, using this requires that the caller posses the NET_CAP_ADMIN capability, though if you +// do this within an container, it only requires the NS_CAPABLE capability for manipulations to that +// container's network namespace. +// Uses the hierarchical token bucket queuing discipline (htb), this requires Linux 2.4.20 or newer +// or a custom kernel with that queuing discipline backported. +type tcShaper struct { + e exec.Interface + iface string +} + +func NewTCShaper(iface string) BandwidthShaper { + shaper := &tcShaper{ + e: exec.New(), + iface: iface, + } + return shaper +} + +func (t *tcShaper) execAndLog(cmdStr string, args ...string) error { + klog.V(6).Infof("Running: %s %s", cmdStr, strings.Join(args, " ")) + cmd := t.e.Command(cmdStr, args...) + out, err := cmd.CombinedOutput() + klog.V(6).Infof("Output from tc: %s", string(out)) + return err +} + +func (t *tcShaper) nextClassID() (int, error) { + data, err := t.e.Command("tc", "class", "show", "dev", t.iface).CombinedOutput() + if err != nil { + return -1, err + } + + scanner := bufio.NewScanner(bytes.NewBuffer(data)) + classes := sets.String{} + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + // skip empty lines + if len(line) == 0 { + continue + } + parts := strings.Split(line, " ") + // expected tc line: + // class htb 1:1 root prio 0 rate 1000Kbit ceil 1000Kbit burst 1600b cburst 1600b + if len(parts) != 14 { + return -1, fmt.Errorf("unexpected output from tc: %s (%v)", scanner.Text(), parts) + } + classes.Insert(parts[2]) + } + + // Make sure it doesn't go forever + for nextClass := 1; nextClass < 10000; nextClass++ { + if !classes.Has(fmt.Sprintf("1:%d", nextClass)) { + return nextClass, nil + } + } + // This should really never happen + return -1, fmt.Errorf("exhausted class space, please try again") +} + +// Convert a CIDR from text to a hex representation +// Strips any masked parts of the IP, so 1.2.3.4/16 becomes hex(1.2.0.0)/ffffffff +func hexCIDR(cidr string) (string, error) { + ip, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return "", err + } + ip = ip.Mask(ipnet.Mask) + hexIP := hex.EncodeToString([]byte(ip)) + hexMask := ipnet.Mask.String() + return hexIP + "/" + hexMask, nil +} + +// Convert a CIDR from hex representation to text, opposite of the above. +func asciiCIDR(cidr string) (string, error) { + parts := strings.Split(cidr, "/") + if len(parts) != 2 { + return "", fmt.Errorf("unexpected CIDR format: %s", cidr) + } + ipData, err := hex.DecodeString(parts[0]) + if err != nil { + return "", err + } + ip := net.IP(ipData) + + maskData, err := hex.DecodeString(parts[1]) + if err != nil { + return "", err + } + mask := net.IPMask(maskData) + size, _ := mask.Size() + + return fmt.Sprintf("%s/%d", ip.String(), size), nil +} + +func (t *tcShaper) findCIDRClass(cidr string) (classAndHandleList [][]string, found bool, err error) { + data, err := t.e.Command("tc", "filter", "show", "dev", t.iface).CombinedOutput() + if err != nil { + return classAndHandleList, false, err + } + + hex, err := hexCIDR(cidr) + if err != nil { + return classAndHandleList, false, err + } + spec := fmt.Sprintf("match %s", hex) + + scanner := bufio.NewScanner(bytes.NewBuffer(data)) + filter := "" + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) == 0 { + continue + } + if strings.HasPrefix(line, "filter") { + filter = line + continue + } + if strings.Contains(line, spec) { + parts := strings.Split(filter, " ") + // expected tc line: + // filter parent 1: protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 + if len(parts) != 19 { + return classAndHandleList, false, fmt.Errorf("unexpected output from tc: %s %d (%v)", filter, len(parts), parts) + } else { + resultTmp := []string{parts[18], parts[9]} + classAndHandleList = append(classAndHandleList, resultTmp) + } + } + } + if len(classAndHandleList) > 0 { + return classAndHandleList, true, nil + } + return classAndHandleList, false, nil +} + +func makeKBitString(rsrc *resource.Quantity) string { + return fmt.Sprintf("%dkbit", (rsrc.Value() / 1000)) +} + +func (t *tcShaper) makeNewClass(rate string) (int, error) { + class, err := t.nextClassID() + if err != nil { + return -1, err + } + if err := t.execAndLog("tc", "class", "add", + "dev", t.iface, + "parent", "1:", + "classid", fmt.Sprintf("1:%d", class), + "htb", "rate", rate); err != nil { + return -1, err + } + return class, nil +} + +func (t *tcShaper) Limit(cidr string, upload, download *resource.Quantity) (err error) { + var downloadClass, uploadClass int + if download != nil { + if downloadClass, err = t.makeNewClass(makeKBitString(download)); err != nil { + return err + } + if err := t.execAndLog("tc", "filter", "add", + "dev", t.iface, + "protocol", "ip", + "parent", "1:0", + "prio", "1", "u32", + "match", "ip", "dst", cidr, + "flowid", fmt.Sprintf("1:%d", downloadClass)); err != nil { + return err + } + } + if upload != nil { + if uploadClass, err = t.makeNewClass(makeKBitString(upload)); err != nil { + return err + } + if err := t.execAndLog("tc", "filter", "add", + "dev", t.iface, + "protocol", "ip", + "parent", "1:0", + "prio", "1", "u32", + "match", "ip", "src", cidr, + "flowid", fmt.Sprintf("1:%d", uploadClass)); err != nil { + return err + } + } + return nil +} + +// tests to see if an interface exists, if it does, return true and the status line for the interface +// returns false, "", if an error occurs. +func (t *tcShaper) interfaceExists() (bool, string, error) { + data, err := t.e.Command("tc", "qdisc", "show", "dev", t.iface).CombinedOutput() + if err != nil { + return false, "", err + } + value := strings.TrimSpace(string(data)) + if len(value) == 0 { + return false, "", nil + } + // Newer versions of tc and/or the kernel return the following instead of nothing: + // qdisc noqueue 0: root refcnt 2 + fields := strings.Fields(value) + if len(fields) > 1 && fields[1] == "noqueue" { + return false, "", nil + } + return true, value, nil +} + +func (t *tcShaper) ReconcileCIDR(cidr string, upload, download *resource.Quantity) error { + _, found, err := t.findCIDRClass(cidr) + if err != nil { + return err + } + if !found { + return t.Limit(cidr, upload, download) + } + // TODO: actually check bandwidth limits here + return nil +} + +func (t *tcShaper) ReconcileInterface() error { + exists, output, err := t.interfaceExists() + if err != nil { + return err + } + if !exists { + klog.V(4).Info("Didn't find bandwidth interface, creating") + return t.initializeInterface() + } + fields := strings.Split(output, " ") + if len(fields) < 12 || fields[1] != "htb" || fields[2] != "1:" { + if err := t.deleteInterface(fields[2]); err != nil { + return err + } + return t.initializeInterface() + } + return nil +} + +func (t *tcShaper) initializeInterface() error { + return t.execAndLog("tc", "qdisc", "add", "dev", t.iface, "root", "handle", "1:", "htb", "default", "30") +} + +func (t *tcShaper) Reset(cidr string) error { + classAndHandle, found, err := t.findCIDRClass(cidr) + if err != nil { + return err + } + if !found { + return fmt.Errorf("Failed to find cidr: %s on interface: %s", cidr, t.iface) + } + for i := 0; i < len(classAndHandle); i++ { + if err := t.execAndLog("tc", "filter", "del", + "dev", t.iface, + "parent", "1:", + "proto", "ip", + "prio", "1", + "handle", classAndHandle[i][1], "u32"); err != nil { + return err + } + if err := t.execAndLog("tc", "class", "del", + "dev", t.iface, + "parent", "1:", + "classid", classAndHandle[i][0]); err != nil { + return err + } + } + return nil + +} + +func (t *tcShaper) deleteInterface(class string) error { + return t.execAndLog("tc", "qdisc", "delete", "dev", t.iface, "root", "handle", class) +} + +func (t *tcShaper) GetCIDRs() ([]string, error) { + data, err := t.e.Command("tc", "filter", "show", "dev", t.iface).CombinedOutput() + if err != nil { + return nil, err + } + + result := []string{} + scanner := bufio.NewScanner(bytes.NewBuffer(data)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) == 0 { + continue + } + if strings.Contains(line, "match") { + parts := strings.Split(line, " ") + // expected tc line: + // match at + if len(parts) != 4 { + return nil, fmt.Errorf("unexpected output: %v", parts) + } + cidr, err := asciiCIDR(parts[1]) + if err != nil { + return nil, err + } + result = append(result, cidr) + } + } + return result, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/bandwidth/unsupported.go b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/unsupported.go new file mode 100644 index 000000000..7d556fd64 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/unsupported.go @@ -0,0 +1,52 @@ +// +build !linux + +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bandwidth + +import ( + "errors" + + "k8s.io/apimachinery/pkg/api/resource" +) + +type unsupportedShaper struct { +} + +func NewTCShaper(iface string) BandwidthShaper { + return &unsupportedShaper{} +} + +func (f *unsupportedShaper) Limit(cidr string, egress, ingress *resource.Quantity) error { + return errors.New("unimplemented") +} + +func (f *unsupportedShaper) Reset(cidr string) error { + return nil +} + +func (f *unsupportedShaper) ReconcileInterface() error { + return errors.New("unimplemented") +} + +func (f *unsupportedShaper) ReconcileCIDR(cidr string, egress, ingress *resource.Quantity) error { + return errors.New("unimplemented") +} + +func (f *unsupportedShaper) GetCIDRs() ([]string, error) { + return []string{}, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/bandwidth/utils.go b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/utils.go new file mode 100644 index 000000000..451ab6883 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/bandwidth/utils.go @@ -0,0 +1,65 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bandwidth + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/resource" +) + +var minRsrc = resource.MustParse("1k") +var maxRsrc = resource.MustParse("1P") + +func validateBandwidthIsReasonable(rsrc *resource.Quantity) error { + if rsrc.Value() < minRsrc.Value() { + return fmt.Errorf("resource is unreasonably small (< 1kbit)") + } + if rsrc.Value() > maxRsrc.Value() { + return fmt.Errorf("resoruce is unreasonably large (> 1Pbit)") + } + return nil +} + +func ExtractPodBandwidthResources(podAnnotations map[string]string) (ingress, egress *resource.Quantity, err error) { + if podAnnotations == nil { + return nil, nil, nil + } + str, found := podAnnotations["kubernetes.io/ingress-bandwidth"] + if found { + ingressValue, err := resource.ParseQuantity(str) + if err != nil { + return nil, nil, err + } + ingress = &ingressValue + if err := validateBandwidthIsReasonable(ingress); err != nil { + return nil, nil, err + } + } + str, found = podAnnotations["kubernetes.io/egress-bandwidth"] + if found { + egressValue, err := resource.ParseQuantity(str) + if err != nil { + return nil, nil, err + } + egress = &egressValue + if err := validateBandwidthIsReasonable(egress); err != nil { + return nil, nil, err + } + } + return ingress, egress, nil +} From 4f0948eed56a85566ff3556ffdc466fb8526c403 Mon Sep 17 00:00:00 2001 From: "Johannes M. Scheuermann" Date: Wed, 29 May 2019 08:01:33 +0200 Subject: [PATCH 3/4] Remove capitalized letter in error message Signed-off-by: Johannes M. Scheuermann --- pkg/server/sandbox_run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/server/sandbox_run.go b/pkg/server/sandbox_run.go index bc6bb8dea..2ec45c0cc 100644 --- a/pkg/server/sandbox_run.go +++ b/pkg/server/sandbox_run.go @@ -576,7 +576,7 @@ func (c *criService) setupPod(id string, path string, config *runtime.PodSandbox func toCNIBandWidth(annotations map[string]string) (*cni.BandWidth, error) { ingress, egress, err := bandwidth.ExtractPodBandwidthResources(annotations) if err != nil { - return nil, fmt.Errorf("Error reading pod bandwidth annotations: %v", err) + return nil, errors.Errorf("reaing pod bandwidth annotations: %v", err) } bandWidth := &cni.BandWidth{} From 5e2e7c6f7dd67e2b595e7f9ea6da5372b820f436 Mon Sep 17 00:00:00 2001 From: "Johannes M. Scheuermann" Date: Thu, 30 May 2019 11:44:05 +0200 Subject: [PATCH 4/4] Correct Egress limits and remove unnecessary check Signed-off-by: Johannes M. Scheuermann --- pkg/server/sandbox_run.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/server/sandbox_run.go b/pkg/server/sandbox_run.go index 2ec45c0cc..75630d1b7 100644 --- a/pkg/server/sandbox_run.go +++ b/pkg/server/sandbox_run.go @@ -581,10 +581,6 @@ func toCNIBandWidth(annotations map[string]string) (*cni.BandWidth, error) { bandWidth := &cni.BandWidth{} - if ingress == nil && egress == nil { - return bandWidth, nil - } - if ingress != nil { bandWidth.IngressRate = uint64(ingress.Value()) bandWidth.IngressBurst = math.MaxUint32 @@ -592,7 +588,7 @@ func toCNIBandWidth(annotations map[string]string) (*cni.BandWidth, error) { if egress != nil { bandWidth.EgressRate = uint64(egress.Value()) - bandWidth.EgressRate = math.MaxUint32 + bandWidth.EgressBurst = math.MaxUint32 } return bandWidth, nil