Merge pull request #1151 from johscheuer/add-bandwidth-capability
Initial support for traffic shaping
This commit is contained in:
commit
35e9f39991
@ -19,6 +19,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||||
|
"k8s.io/kubernetes/pkg/util/bandwidth"
|
||||||
|
|
||||||
"github.com/containerd/cri/pkg/annotations"
|
"github.com/containerd/cri/pkg/annotations"
|
||||||
criconfig "github.com/containerd/cri/pkg/config"
|
criconfig "github.com/containerd/cri/pkg/config"
|
||||||
@ -540,10 +542,21 @@ func (c *criService) setupPod(id string, path string, config *runtime.PodSandbox
|
|||||||
}
|
}
|
||||||
|
|
||||||
labels := getPodCNILabels(id, config)
|
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 {
|
||||||
|
return "", nil, errors.Errorf("failed to find network info for sandbox %q", id)
|
||||||
|
}
|
||||||
|
|
||||||
result, err := c.netPlugin.Setup(id,
|
result, err := c.netPlugin.Setup(id,
|
||||||
path,
|
path,
|
||||||
cni.WithLabels(labels),
|
cni.WithLabels(labels),
|
||||||
cni.WithCapabilityPortMap(toCNIPortMappings(config.GetPortMappings())))
|
cni.WithCapabilityPortMap(toCNIPortMappings(config.GetPortMappings())),
|
||||||
|
cni.WithCapabilityBandWidth(*bandWidth),
|
||||||
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@ -559,6 +572,28 @@ func (c *criService) setupPod(id string, path string, config *runtime.PodSandbox
|
|||||||
return "", result, errors.Errorf("failed to find network info for sandbox %q", id)
|
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, errors.Errorf("reaing pod bandwidth annotations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bandWidth := &cni.BandWidth{}
|
||||||
|
|
||||||
|
if ingress != nil {
|
||||||
|
bandWidth.IngressRate = uint64(ingress.Value())
|
||||||
|
bandWidth.IngressBurst = math.MaxUint32
|
||||||
|
}
|
||||||
|
|
||||||
|
if egress != nil {
|
||||||
|
bandWidth.EgressRate = uint64(egress.Value())
|
||||||
|
bandWidth.EgressBurst = math.MaxUint32
|
||||||
|
}
|
||||||
|
|
||||||
|
return bandWidth, nil
|
||||||
|
}
|
||||||
|
|
||||||
// toCNIPortMappings converts CRI port mappings to CNI.
|
// toCNIPortMappings converts CRI port mappings to CNI.
|
||||||
func toCNIPortMappings(criPortMappings []*runtime.PortMapping) []cni.PortMapping {
|
func toCNIPortMappings(criPortMappings []*runtime.PortMapping) []cni.PortMapping {
|
||||||
var portMappings []cni.PortMapping
|
var portMappings []cni.PortMapping
|
||||||
|
@ -5,7 +5,7 @@ github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
|||||||
github.com/containerd/containerd 32e788a8be3ab4418265693d9e742c30495fdd4c
|
github.com/containerd/containerd 32e788a8be3ab4418265693d9e742c30495fdd4c
|
||||||
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
||||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
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/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
||||||
github.com/containerd/ttrpc f02858b1457c5ca3aaec3a0803eb0d59f96e41d6
|
github.com/containerd/ttrpc f02858b1457c5ca3aaec3a0803eb0d59f96e41d6
|
||||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||||
|
9
vendor/github.com/containerd/go-cni/namespace_opts.go
generated
vendored
9
vendor/github.com/containerd/go-cni/namespace_opts.go
generated
vendored
@ -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 {
|
func WithCapability(name string, capability interface{}) NamespaceOpts {
|
||||||
return func(c *Namespace) error {
|
return func(c *Namespace) error {
|
||||||
c.capabilityArgs[name] = capability
|
c.capabilityArgs[name] = capability
|
||||||
|
8
vendor/github.com/containerd/go-cni/opts.go
generated
vendored
8
vendor/github.com/containerd/go-cni/opts.go
generated
vendored
@ -86,6 +86,12 @@ func WithLoNetwork(c *libcni) error {
|
|||||||
// WithConf can be used to load config directly
|
// WithConf can be used to load config directly
|
||||||
// from byte.
|
// from byte.
|
||||||
func WithConf(bytes []byte) CNIOpt {
|
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 {
|
return func(c *libcni) error {
|
||||||
conf, err := cnilibrary.ConfFromBytes(bytes)
|
conf, err := cnilibrary.ConfFromBytes(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -98,7 +104,7 @@ func WithConf(bytes []byte) CNIOpt {
|
|||||||
c.networks = append(c.networks, &Network{
|
c.networks = append(c.networks, &Network{
|
||||||
cni: c.cniConfig,
|
cni: c.cniConfig,
|
||||||
config: confList,
|
config: confList,
|
||||||
ifName: getIfName(c.prefix, 0),
|
ifName: getIfName(c.prefix, index),
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
8
vendor/github.com/containerd/go-cni/types.go
generated
vendored
8
vendor/github.com/containerd/go-cni/types.go
generated
vendored
@ -43,3 +43,11 @@ type IPRanges struct {
|
|||||||
RangeEnd string
|
RangeEnd string
|
||||||
Gateway string
|
Gateway string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BandWidth defines the ingress/egress rate and burst limits
|
||||||
|
type BandWidth struct {
|
||||||
|
IngressRate uint64
|
||||||
|
IngressBurst uint64
|
||||||
|
EgressRate uint64
|
||||||
|
EgressBurst uint64
|
||||||
|
}
|
||||||
|
18
vendor/k8s.io/kubernetes/pkg/util/bandwidth/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/util/bandwidth/doc.go
generated
vendored
Normal file
@ -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"
|
49
vendor/k8s.io/kubernetes/pkg/util/bandwidth/fake_shaper.go
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/pkg/util/bandwidth/fake_shaper.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
38
vendor/k8s.io/kubernetes/pkg/util/bandwidth/interfaces.go
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/pkg/util/bandwidth/interfaces.go
generated
vendored
Normal file
@ -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)
|
||||||
|
}
|
339
vendor/k8s.io/kubernetes/pkg/util/bandwidth/linux.go
generated
vendored
Normal file
339
vendor/k8s.io/kubernetes/pkg/util/bandwidth/linux.go
generated
vendored
Normal file
@ -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, "", <err> 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 <cidr> at <number>
|
||||||
|
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
|
||||||
|
}
|
52
vendor/k8s.io/kubernetes/pkg/util/bandwidth/unsupported.go
generated
vendored
Normal file
52
vendor/k8s.io/kubernetes/pkg/util/bandwidth/unsupported.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
65
vendor/k8s.io/kubernetes/pkg/util/bandwidth/utils.go
generated
vendored
Normal file
65
vendor/k8s.io/kubernetes/pkg/util/bandwidth/utils.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user