Vendor cleanup

Signed-off-by: Madhu Rajanna <mrajanna@redhat.com>
This commit is contained in:
Madhu Rajanna
2019-01-16 18:11:54 +05:30
parent 661818bd79
commit 0f836c62fa
16816 changed files with 20 additions and 4611100 deletions

View File

@ -1,73 +0,0 @@
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/util/async:all-srcs",
"//pkg/util/bandwidth:all-srcs",
"//pkg/util/config:all-srcs",
"//pkg/util/configz:all-srcs",
"//pkg/util/conntrack:all-srcs",
"//pkg/util/coverage:all-srcs",
"//pkg/util/dbus:all-srcs",
"//pkg/util/ebtables:all-srcs",
"//pkg/util/env:all-srcs",
"//pkg/util/file:all-srcs",
"//pkg/util/filesystem:all-srcs",
"//pkg/util/flag:all-srcs",
"//pkg/util/flock:all-srcs",
"//pkg/util/goroutinemap:all-srcs",
"//pkg/util/hash:all-srcs",
"//pkg/util/initsystem:all-srcs",
"//pkg/util/interrupt:all-srcs",
"//pkg/util/io:all-srcs",
"//pkg/util/ipconfig:all-srcs",
"//pkg/util/ipset:all-srcs",
"//pkg/util/iptables:all-srcs",
"//pkg/util/ipvs:all-srcs",
"//pkg/util/keymutex:all-srcs",
"//pkg/util/labels:all-srcs",
"//pkg/util/maps:all-srcs",
"//pkg/util/metrics:all-srcs",
"//pkg/util/mount:all-srcs",
"//pkg/util/net:all-srcs",
"//pkg/util/netsh:all-srcs",
"//pkg/util/node:all-srcs",
"//pkg/util/normalizer:all-srcs",
"//pkg/util/nsenter:all-srcs",
"//pkg/util/oom:all-srcs",
"//pkg/util/parsers:all-srcs",
"//pkg/util/pod:all-srcs",
"//pkg/util/procfs:all-srcs",
"//pkg/util/reflector/prometheus:all-srcs",
"//pkg/util/removeall:all-srcs",
"//pkg/util/resizefs:all-srcs",
"//pkg/util/resourcecontainer:all-srcs",
"//pkg/util/rlimit:all-srcs",
"//pkg/util/selinux:all-srcs",
"//pkg/util/slice:all-srcs",
"//pkg/util/strings:all-srcs",
"//pkg/util/sysctl:all-srcs",
"//pkg/util/system:all-srcs",
"//pkg/util/tail:all-srcs",
"//pkg/util/taints:all-srcs",
"//pkg/util/tolerations:all-srcs",
"//pkg/util/workqueue/prometheus:all-srcs",
],
tags = ["automanaged"],
)
sh_test(
name = "verify-util-pkg",
size = "small",
srcs = ["verify-util-pkg.sh"],
data = glob(["*.go"]),
)

View File

@ -1,42 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"bounded_frequency_runner.go",
"runner.go",
],
importpath = "k8s.io/kubernetes/pkg/util/async",
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"bounded_frequency_runner_test.go",
"runner_test.go",
],
embed = [":go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,239 +0,0 @@
/*
Copyright 2017 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 async
import (
"fmt"
"sync"
"time"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog"
)
// BoundedFrequencyRunner manages runs of a user-provided function.
// See NewBoundedFrequencyRunner for examples.
type BoundedFrequencyRunner struct {
name string // the name of this instance
minInterval time.Duration // the min time between runs, modulo bursts
maxInterval time.Duration // the max time between runs
run chan struct{} // try an async run
mu sync.Mutex // guards runs of fn and all mutations
fn func() // function to run
lastRun time.Time // time of last run
timer timer // timer for deferred runs
limiter rateLimiter // rate limiter for on-demand runs
}
// designed so that flowcontrol.RateLimiter satisfies
type rateLimiter interface {
TryAccept() bool
Stop()
}
type nullLimiter struct{}
func (nullLimiter) TryAccept() bool {
return true
}
func (nullLimiter) Stop() {}
var _ rateLimiter = nullLimiter{}
// for testing
type timer interface {
// C returns the timer's selectable channel.
C() <-chan time.Time
// See time.Timer.Reset.
Reset(d time.Duration) bool
// See time.Timer.Stop.
Stop() bool
// See time.Now.
Now() time.Time
// See time.Since.
Since(t time.Time) time.Duration
// See time.Sleep.
Sleep(d time.Duration)
}
// implement our timer in terms of std time.Timer.
type realTimer struct {
*time.Timer
}
func (rt realTimer) C() <-chan time.Time {
return rt.Timer.C
}
func (rt realTimer) Now() time.Time {
return time.Now()
}
func (rt realTimer) Since(t time.Time) time.Duration {
return time.Since(t)
}
func (rt realTimer) Sleep(d time.Duration) {
time.Sleep(d)
}
var _ timer = realTimer{}
// NewBoundedFrequencyRunner creates a new BoundedFrequencyRunner instance,
// which will manage runs of the specified function.
//
// All runs will be async to the caller of BoundedFrequencyRunner.Run, but
// multiple runs are serialized. If the function needs to hold locks, it must
// take them internally.
//
// Runs of the function will have at least minInterval between them (from
// completion to next start), except that up to bursts may be allowed. Burst
// runs are "accumulated" over time, one per minInterval up to burstRuns total.
// This can be used, for example, to mitigate the impact of expensive operations
// being called in response to user-initiated operations. Run requests that
// would violate the minInterval are coallesced and run at the next opportunity.
//
// The function will be run at least once per maxInterval. For example, this can
// force periodic refreshes of state in the absence of anyone calling Run.
//
// Examples:
//
// NewBoundedFrequencyRunner("name", fn, time.Second, 5*time.Second, 1)
// - fn will have at least 1 second between runs
// - fn will have no more than 5 seconds between runs
//
// NewBoundedFrequencyRunner("name", fn, 3*time.Second, 10*time.Second, 3)
// - fn will have at least 3 seconds between runs, with up to 3 burst runs
// - fn will have no more than 10 seconds between runs
//
// The maxInterval must be greater than or equal to the minInterval, If the
// caller passes a maxInterval less than minInterval, this function will panic.
func NewBoundedFrequencyRunner(name string, fn func(), minInterval, maxInterval time.Duration, burstRuns int) *BoundedFrequencyRunner {
timer := realTimer{Timer: time.NewTimer(0)} // will tick immediately
<-timer.C() // consume the first tick
return construct(name, fn, minInterval, maxInterval, burstRuns, timer)
}
// Make an instance with dependencies injected.
func construct(name string, fn func(), minInterval, maxInterval time.Duration, burstRuns int, timer timer) *BoundedFrequencyRunner {
if maxInterval < minInterval {
panic(fmt.Sprintf("%s: maxInterval (%v) must be >= minInterval (%v)", name, minInterval, maxInterval))
}
if timer == nil {
panic(fmt.Sprintf("%s: timer must be non-nil", name))
}
bfr := &BoundedFrequencyRunner{
name: name,
fn: fn,
minInterval: minInterval,
maxInterval: maxInterval,
run: make(chan struct{}, 1),
timer: timer,
}
if minInterval == 0 {
bfr.limiter = nullLimiter{}
} else {
// allow burst updates in short succession
qps := float32(time.Second) / float32(minInterval)
bfr.limiter = flowcontrol.NewTokenBucketRateLimiterWithClock(qps, burstRuns, timer)
}
return bfr
}
// Loop handles the periodic timer and run requests. This is expected to be
// called as a goroutine.
func (bfr *BoundedFrequencyRunner) Loop(stop <-chan struct{}) {
klog.V(3).Infof("%s Loop running", bfr.name)
bfr.timer.Reset(bfr.maxInterval)
for {
select {
case <-stop:
bfr.stop()
klog.V(3).Infof("%s Loop stopping", bfr.name)
return
case <-bfr.timer.C():
bfr.tryRun()
case <-bfr.run:
bfr.tryRun()
}
}
}
// Run the function as soon as possible. If this is called while Loop is not
// running, the call may be deferred indefinitely.
// If there is already a queued request to call the underlying function, it
// may be dropped - it is just guaranteed that we will try calling the
// underlying function as soon as possible starting from now.
func (bfr *BoundedFrequencyRunner) Run() {
// If it takes a lot of time to run the underlying function, noone is really
// processing elements from <run> channel. So to avoid blocking here on the
// putting element to it, we simply skip it if there is already an element
// in it.
select {
case bfr.run <- struct{}{}:
default:
}
}
// assumes the lock is not held
func (bfr *BoundedFrequencyRunner) stop() {
bfr.mu.Lock()
defer bfr.mu.Unlock()
bfr.limiter.Stop()
bfr.timer.Stop()
}
// assumes the lock is not held
func (bfr *BoundedFrequencyRunner) tryRun() {
bfr.mu.Lock()
defer bfr.mu.Unlock()
if bfr.limiter.TryAccept() {
// We're allowed to run the function right now.
bfr.fn()
bfr.lastRun = bfr.timer.Now()
bfr.timer.Stop()
bfr.timer.Reset(bfr.maxInterval)
klog.V(3).Infof("%s: ran, next possible in %v, periodic in %v", bfr.name, bfr.minInterval, bfr.maxInterval)
return
}
// It can't run right now, figure out when it can run next.
elapsed := bfr.timer.Since(bfr.lastRun) // how long since last run
nextPossible := bfr.minInterval - elapsed // time to next possible run
nextScheduled := bfr.maxInterval - elapsed // time to next periodic run
klog.V(4).Infof("%s: %v since last run, possible in %v, scheduled in %v", bfr.name, elapsed, nextPossible, nextScheduled)
if nextPossible < nextScheduled {
// Set the timer for ASAP, but don't drain here. Assuming Loop is running,
// it might get a delivery in the mean time, but that is OK.
bfr.timer.Stop()
bfr.timer.Reset(nextPossible)
klog.V(3).Infof("%s: throttled, scheduling run in %v", bfr.name, nextPossible)
}
}

View File

@ -1,333 +0,0 @@
/*
Copyright 2017 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 async
import (
"sync"
"testing"
"time"
)
// Track calls to the managed function.
type receiver struct {
lock sync.Mutex
run bool
}
func (r *receiver) F() {
r.lock.Lock()
defer r.lock.Unlock()
r.run = true
}
func (r *receiver) reset() bool {
r.lock.Lock()
defer r.lock.Unlock()
was := r.run
r.run = false
return was
}
// A single change event in the fake timer.
type timerUpdate struct {
active bool
next time.Duration // iff active == true
}
// Fake time.
type fakeTimer struct {
c chan time.Time
lock sync.Mutex
now time.Time
active bool
updated chan timerUpdate
}
func newFakeTimer() *fakeTimer {
ft := &fakeTimer{
now: time.Date(2000, 0, 0, 0, 0, 0, 0, time.UTC),
c: make(chan time.Time),
updated: make(chan timerUpdate),
}
return ft
}
func (ft *fakeTimer) C() <-chan time.Time {
return ft.c
}
func (ft *fakeTimer) Reset(in time.Duration) bool {
ft.lock.Lock()
defer ft.lock.Unlock()
was := ft.active
ft.active = true
ft.updated <- timerUpdate{
active: true,
next: in,
}
return was
}
func (ft *fakeTimer) Stop() bool {
ft.lock.Lock()
defer ft.lock.Unlock()
was := ft.active
ft.active = false
ft.updated <- timerUpdate{
active: false,
}
return was
}
func (ft *fakeTimer) Now() time.Time {
ft.lock.Lock()
defer ft.lock.Unlock()
return ft.now
}
func (ft *fakeTimer) Since(t time.Time) time.Duration {
ft.lock.Lock()
defer ft.lock.Unlock()
return ft.now.Sub(t)
}
func (ft *fakeTimer) Sleep(d time.Duration) {
ft.lock.Lock()
defer ft.lock.Unlock()
ft.advance(d)
}
// advance the current time.
func (ft *fakeTimer) advance(d time.Duration) {
ft.lock.Lock()
defer ft.lock.Unlock()
ft.now = ft.now.Add(d)
}
// send a timer tick.
func (ft *fakeTimer) tick() {
ft.lock.Lock()
defer ft.lock.Unlock()
ft.active = false
ft.c <- ft.now
}
// return the calling line number (for printing)
// test the timer's state
func checkTimer(name string, t *testing.T, upd timerUpdate, active bool, next time.Duration) {
if upd.active != active {
t.Fatalf("%s: expected timer active=%v", name, active)
}
if active && upd.next != next {
t.Fatalf("%s: expected timer to be %v, got %v", name, next, upd.next)
}
}
// test and reset the receiver's state
func checkReceiver(name string, t *testing.T, receiver *receiver, expected bool) {
triggered := receiver.reset()
if expected && !triggered {
t.Fatalf("%s: function should have been called", name)
} else if !expected && triggered {
t.Fatalf("%s: function should not have been called", name)
}
}
// Durations embedded in test cases depend on these.
var minInterval = 1 * time.Second
var maxInterval = 10 * time.Second
func waitForReset(name string, t *testing.T, timer *fakeTimer, obj *receiver, expectCall bool, expectNext time.Duration) {
upd := <-timer.updated // wait for stop
checkReceiver(name, t, obj, expectCall)
checkReceiver(name, t, obj, false) // prove post-condition
checkTimer(name, t, upd, false, 0)
upd = <-timer.updated // wait for reset
checkTimer(name, t, upd, true, expectNext)
}
func waitForRun(name string, t *testing.T, timer *fakeTimer, obj *receiver) {
waitForReset(name, t, timer, obj, true, maxInterval)
}
func waitForDefer(name string, t *testing.T, timer *fakeTimer, obj *receiver, expectNext time.Duration) {
waitForReset(name, t, timer, obj, false, expectNext)
}
func Test_BoundedFrequencyRunnerNoBurst(t *testing.T) {
obj := &receiver{}
timer := newFakeTimer()
runner := construct("test-runner", obj.F, minInterval, maxInterval, 1, timer)
stop := make(chan struct{})
var upd timerUpdate
// Start.
go runner.Loop(stop)
upd = <-timer.updated // wait for initial time to be set to max
checkTimer("init", t, upd, true, maxInterval)
checkReceiver("init", t, obj, false)
// Run once, immediately.
// rel=0ms
runner.Run()
waitForRun("first run", t, timer, obj)
// Run again, before minInterval expires.
timer.advance(500 * time.Millisecond) // rel=500ms
runner.Run()
waitForDefer("too soon after first", t, timer, obj, 500*time.Millisecond)
// Run again, before minInterval expires.
timer.advance(499 * time.Millisecond) // rel=999ms
runner.Run()
waitForDefer("still too soon after first", t, timer, obj, 1*time.Millisecond)
// Run again, once minInterval has passed (race with timer).
timer.advance(1 * time.Millisecond) // rel=1000ms
runner.Run()
waitForRun("second run", t, timer, obj)
// Run again, before minInterval expires.
// rel=0ms
runner.Run()
waitForDefer("too soon after second", t, timer, obj, 1*time.Second)
// Run again, before minInterval expires.
timer.advance(1 * time.Millisecond) // rel=1ms
runner.Run()
waitForDefer("still too soon after second", t, timer, obj, 999*time.Millisecond)
// Let the timer tick prematurely.
timer.advance(998 * time.Millisecond) // rel=999ms
timer.tick()
waitForDefer("premature tick", t, timer, obj, 1*time.Millisecond)
// Let the timer tick.
timer.advance(1 * time.Millisecond) // rel=1000ms
timer.tick()
waitForRun("first tick", t, timer, obj)
// Let the timer tick.
timer.advance(10 * time.Second) // rel=10000ms
timer.tick()
waitForRun("second tick", t, timer, obj)
// Run again, before minInterval expires.
timer.advance(1 * time.Millisecond) // rel=1ms
runner.Run()
waitForDefer("too soon after tick", t, timer, obj, 999*time.Millisecond)
// Let the timer tick.
timer.advance(999 * time.Millisecond) // rel=1000ms
timer.tick()
waitForRun("third tick", t, timer, obj)
// Clean up.
stop <- struct{}{}
}
func Test_BoundedFrequencyRunnerBurst(t *testing.T) {
obj := &receiver{}
timer := newFakeTimer()
runner := construct("test-runner", obj.F, minInterval, maxInterval, 2, timer)
stop := make(chan struct{})
var upd timerUpdate
// Start.
go runner.Loop(stop)
upd = <-timer.updated // wait for initial time to be set to max
checkTimer("init", t, upd, true, maxInterval)
checkReceiver("init", t, obj, false)
// Run once, immediately.
// abs=0ms, rel=0ms
runner.Run()
waitForRun("first run", t, timer, obj)
// Run again, before minInterval expires, with burst.
timer.advance(1 * time.Millisecond) // abs=1ms, rel=1ms
runner.Run()
waitForRun("second run", t, timer, obj)
// Run again, before minInterval expires.
timer.advance(498 * time.Millisecond) // abs=499ms, rel=498ms
runner.Run()
waitForDefer("too soon after second", t, timer, obj, 502*time.Millisecond)
// Run again, before minInterval expires.
timer.advance(1 * time.Millisecond) // abs=500ms, rel=499ms
runner.Run()
waitForDefer("too soon after second 2", t, timer, obj, 501*time.Millisecond)
// Run again, before minInterval expires.
timer.advance(1 * time.Millisecond) // abs=501ms, rel=500ms
runner.Run()
waitForDefer("too soon after second 3", t, timer, obj, 500*time.Millisecond)
// Run again, once burst has replenished.
timer.advance(499 * time.Millisecond) // abs=1000ms, rel=999ms
runner.Run()
waitForRun("third run", t, timer, obj)
// Run again, before minInterval expires.
timer.advance(1 * time.Millisecond) // abs=1001ms, rel=1ms
runner.Run()
waitForDefer("too soon after third", t, timer, obj, 999*time.Millisecond)
// Run again, before minInterval expires.
timer.advance(998 * time.Millisecond) // abs=1999ms, rel=999ms
runner.Run()
waitForDefer("too soon after third 2", t, timer, obj, 1*time.Millisecond)
// Run again, once burst has replenished.
timer.advance(1 * time.Millisecond) // abs=2000ms, rel=1000ms
runner.Run()
waitForRun("fourth run", t, timer, obj)
// Run again, once burst has fully replenished.
timer.advance(2 * time.Second) // abs=4000ms, rel=2000ms
runner.Run()
waitForRun("fifth run", t, timer, obj)
runner.Run()
waitForRun("sixth run", t, timer, obj)
runner.Run()
waitForDefer("too soon after sixth", t, timer, obj, 1*time.Second)
// Let the timer tick.
timer.advance(1 * time.Second) // abs=5000ms, rel=1000ms
timer.tick()
waitForRun("first tick", t, timer, obj)
// Let the timer tick.
timer.advance(10 * time.Second) // abs=15000ms, rel=10000ms
timer.tick()
waitForRun("second tick", t, timer, obj)
// Clean up.
stop <- struct{}{}
}

View File

@ -1,58 +0,0 @@
/*
Copyright 2014 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 async
import (
"sync"
)
// Runner is an abstraction to make it easy to start and stop groups of things that can be
// described by a single function which waits on a channel close to exit.
type Runner struct {
lock sync.Mutex
loopFuncs []func(stop chan struct{})
stop *chan struct{}
}
// NewRunner makes a runner for the given function(s). The function(s) should loop until
// the channel is closed.
func NewRunner(f ...func(stop chan struct{})) *Runner {
return &Runner{loopFuncs: f}
}
// Start begins running.
func (r *Runner) Start() {
r.lock.Lock()
defer r.lock.Unlock()
if r.stop == nil {
c := make(chan struct{})
r.stop = &c
for i := range r.loopFuncs {
go r.loopFuncs[i](*r.stop)
}
}
}
// Stop stops running.
func (r *Runner) Stop() {
r.lock.Lock()
defer r.lock.Unlock()
if r.stop != nil {
close(*r.stop)
r.stop = nil
}
}

View File

@ -1,55 +0,0 @@
/*
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 async
import (
"fmt"
"sync"
"testing"
)
func TestRunner(t *testing.T) {
var (
lock sync.Mutex
events []string
funcs []func(chan struct{})
)
done := make(chan struct{}, 20)
for i := 0; i < 10; i++ {
iCopy := i
funcs = append(funcs, func(c chan struct{}) {
lock.Lock()
events = append(events, fmt.Sprintf("%v starting\n", iCopy))
lock.Unlock()
<-c
lock.Lock()
events = append(events, fmt.Sprintf("%v stopping\n", iCopy))
lock.Unlock()
done <- struct{}{}
})
}
r := NewRunner(funcs...)
r.Start()
r.Stop()
for i := 0; i < 10; i++ {
<-done
}
if len(events) != 20 {
t.Errorf("expected 20 events, but got:\n%v\n", events)
}
}

View File

@ -1,63 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"fake_shaper.go",
"interfaces.go",
"linux.go",
"unsupported.go",
"utils.go",
],
importpath = "k8s.io/kubernetes/pkg/util/bandwidth",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux": [
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
"//conditions:default": [],
}),
)
go_test(
name = "go_default_test",
srcs = [
"linux_test.go",
"utils_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,18 +0,0 @@
/*
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"

View File

@ -1,49 +0,0 @@
/*
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
}

View File

@ -1,38 +0,0 @@
/*
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)
}

View File

@ -1,339 +0,0 @@
// +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
}

View File

@ -1,704 +0,0 @@
// +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"
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/exec"
fakeexec "k8s.io/utils/exec/testing"
)
var tcClassOutput = `class htb 1:1 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
class htb 1:2 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
class htb 1:3 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
class htb 1:4 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
`
var tcClassOutput2 = `class htb 1:1 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
class htb 1:2 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
class htb 1:3 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
class htb 1:4 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
class htb 1:5 root prio 0 rate 10000bit ceil 10000bit burst 1600b cburst 1600b
`
func TestNextClassID(t *testing.T) {
tests := []struct {
output string
expectErr bool
expected int
err error
}{
{
output: tcClassOutput,
expected: 5,
},
{
output: "\n",
expected: 1,
},
{
expected: -1,
expectErr: true,
err: errors.New("test error"),
},
}
for _, test := range tests {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(test.output), test.err },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd {
return fakeexec.InitFakeCmd(&fcmd, cmd, args...)
},
},
}
shaper := &tcShaper{e: &fexec}
class, err := shaper.nextClassID()
if test.expectErr {
if err == nil {
t.Errorf("unexpected non-error")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if class != test.expected {
t.Errorf("expected: %d, found %d", test.expected, class)
}
}
}
}
func TestHexCIDR(t *testing.T) {
tests := []struct {
name string
input string
output string
expectErr bool
}{
{
name: "IPv4 masked",
input: "1.2.3.4/16",
output: "01020000/ffff0000",
},
{
name: "IPv4 host",
input: "172.17.0.2/32",
output: "ac110002/ffffffff",
},
{
name: "IPv6 masked",
input: "2001:dead:beef::cafe/64",
output: "2001deadbeef00000000000000000000/ffffffffffffffff0000000000000000",
},
{
name: "IPv6 host",
input: "2001::5/128",
output: "20010000000000000000000000000005/ffffffffffffffffffffffffffffffff",
},
{
name: "invalid CIDR",
input: "foo",
expectErr: true,
},
}
for _, test := range tests {
output, err := hexCIDR(test.input)
if test.expectErr {
if err == nil {
t.Errorf("case %s: unexpected non-error", test.name)
}
} else {
if err != nil {
t.Errorf("case %s: unexpected error: %v", test.name, err)
}
if output != test.output {
t.Errorf("case %s: expected: %s, saw: %s",
test.name, test.output, output)
}
}
}
}
func TestAsciiCIDR(t *testing.T) {
tests := []struct {
name string
input string
output string
expectErr bool
}{
{
name: "IPv4",
input: "01020000/ffff0000",
output: "1.2.0.0/16",
},
{
name: "IPv4 host",
input: "ac110002/ffffffff",
output: "172.17.0.2/32",
},
{
name: "IPv6",
input: "2001deadbeef00000000000000000000/ffffffffffffffff0000000000000000",
output: "2001:dead:beef::/64",
},
{
name: "IPv6 host",
input: "20010000000000000000000000000005/ffffffffffffffffffffffffffffffff",
output: "2001::5/128",
},
{
name: "invalid CIDR",
input: "malformed",
expectErr: true,
},
{
name: "non-hex IP",
input: "nonhex/32",
expectErr: true,
},
{
name: "non-hex mask",
input: "01020000/badmask",
expectErr: true,
},
}
for _, test := range tests {
output, err := asciiCIDR(test.input)
if test.expectErr {
if err == nil {
t.Errorf("case %s: unexpected non-error", test.name)
}
} else {
if err != nil {
t.Errorf("case %s: unexpected error: %v", test.name, err)
}
if output != test.output {
t.Errorf("case %s: expected: %s, saw: %s",
test.name, test.output, output)
}
}
}
}
var tcFilterOutput = `filter parent 1: protocol ip pref 1 u32
filter parent 1: protocol ip pref 1 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
match ac110002/ffffffff at 16
filter parent 1: protocol ip pref 1 u32 fh 800::801 order 2049 key ht 800 bkt 0 flowid 1:2
match 01020000/ffff0000 at 16
`
func TestFindCIDRClass(t *testing.T) {
tests := []struct {
cidr string
output string
expectErr bool
expectNotFound bool
expectedClass string
expectedHandle string
err error
}{
{
cidr: "172.17.0.2/32",
output: tcFilterOutput,
expectedClass: "1:1",
expectedHandle: "800::800",
},
{
cidr: "1.2.3.4/16",
output: tcFilterOutput,
expectedClass: "1:2",
expectedHandle: "800::801",
},
{
cidr: "2.2.3.4/16",
output: tcFilterOutput,
expectNotFound: true,
},
{
err: errors.New("test error"),
expectErr: true,
},
}
for _, test := range tests {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(test.output), test.err },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd {
return fakeexec.InitFakeCmd(&fcmd, cmd, args...)
},
},
}
shaper := &tcShaper{e: &fexec}
classAndHandle, found, err := shaper.findCIDRClass(test.cidr)
if test.expectErr {
if err == nil {
t.Errorf("unexpected non-error")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if test.expectNotFound {
if found {
t.Errorf("unexpectedly found an interface: %s", classAndHandle)
}
} else {
if classAndHandle[0][0] != test.expectedClass {
t.Errorf("expected class: %s, found %s", test.expectedClass, classAndHandle)
}
if classAndHandle[0][1] != test.expectedHandle {
t.Errorf("expected handle: %s, found %s", test.expectedHandle, classAndHandle)
}
}
}
}
}
func TestGetCIDRs(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(tcFilterOutput), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd {
return fakeexec.InitFakeCmd(&fcmd, cmd, args...)
},
},
}
shaper := &tcShaper{e: &fexec}
cidrs, err := shaper.GetCIDRs()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedCidrs := []string{"172.17.0.2/32", "1.2.0.0/16"}
if !reflect.DeepEqual(cidrs, expectedCidrs) {
t.Errorf("expected: %v, saw: %v", expectedCidrs, cidrs)
}
}
func TestLimit(t *testing.T) {
tests := []struct {
cidr string
ingress *resource.Quantity
egress *resource.Quantity
expectErr bool
expectedCalls int
err error
}{
{
cidr: "1.2.3.4/32",
ingress: resource.NewQuantity(10, resource.DecimalSI),
egress: resource.NewQuantity(20, resource.DecimalSI),
expectedCalls: 6,
},
{
cidr: "1.2.3.4/32",
ingress: resource.NewQuantity(10, resource.DecimalSI),
egress: nil,
expectedCalls: 3,
},
{
cidr: "1.2.3.4/32",
ingress: nil,
egress: resource.NewQuantity(20, resource.DecimalSI),
expectedCalls: 3,
},
{
cidr: "1.2.3.4/32",
ingress: nil,
egress: nil,
expectedCalls: 0,
},
{
err: errors.New("test error"),
ingress: resource.NewQuantity(10, resource.DecimalSI),
egress: resource.NewQuantity(20, resource.DecimalSI),
expectErr: true,
},
}
for _, test := range tests {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(tcClassOutput), test.err },
func() ([]byte, error) { return []byte{}, test.err },
func() ([]byte, error) { return []byte{}, test.err },
func() ([]byte, error) { return []byte(tcClassOutput2), test.err },
func() ([]byte, error) { return []byte{}, test.err },
func() ([]byte, error) { return []byte{}, test.err },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
iface := "cbr0"
shaper := &tcShaper{e: &fexec, iface: iface}
if err := shaper.Limit(test.cidr, test.ingress, test.egress); err != nil && !test.expectErr {
t.Errorf("unexpected error: %v", err)
return
} else if err == nil && test.expectErr {
t.Error("unexpected non-error")
return
}
// No more testing in the error case
if test.expectErr {
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("unexpected number of calls: %d, expected: 1", fcmd.CombinedOutputCalls)
}
return
}
if fcmd.CombinedOutputCalls != test.expectedCalls {
t.Errorf("unexpected number of calls: %d, expected: %d", fcmd.CombinedOutputCalls, test.expectedCalls)
}
for ix := range fcmd.CombinedOutputLog {
output := fcmd.CombinedOutputLog[ix]
if output[0] != "tc" {
t.Errorf("unexpected command: %s, expected tc", output[0])
}
if output[4] != iface {
t.Errorf("unexpected interface: %s, expected %s (%v)", output[4], iface, output)
}
if ix == 1 {
var expectedRate string
if test.ingress != nil {
expectedRate = makeKBitString(test.ingress)
} else {
expectedRate = makeKBitString(test.egress)
}
if output[11] != expectedRate {
t.Errorf("unexpected ingress: %s, expected: %s", output[11], expectedRate)
}
if output[8] != "1:5" {
t.Errorf("unexpected class: %s, expected: %s", output[8], "1:5")
}
}
if ix == 2 {
if output[15] != test.cidr {
t.Errorf("unexpected cidr: %s, expected: %s", output[15], test.cidr)
}
if output[17] != "1:5" {
t.Errorf("unexpected class: %s, expected: %s", output[17], "1:5")
}
}
if ix == 4 {
if output[11] != makeKBitString(test.egress) {
t.Errorf("unexpected egress: %s, expected: %s", output[11], makeKBitString(test.egress))
}
if output[8] != "1:6" {
t.Errorf("unexpected class: %s, expected: %s", output[8], "1:6")
}
}
if ix == 5 {
if output[15] != test.cidr {
t.Errorf("unexpected cidr: %s, expected: %s", output[15], test.cidr)
}
if output[17] != "1:6" {
t.Errorf("unexpected class: %s, expected: %s", output[17], "1:5")
}
}
}
}
}
func TestReset(t *testing.T) {
tests := []struct {
cidr string
err error
expectErr bool
expectedHandle string
expectedClass string
}{
{
cidr: "1.2.3.4/16",
expectedHandle: "800::801",
expectedClass: "1:2",
},
{
cidr: "172.17.0.2/32",
expectedHandle: "800::800",
expectedClass: "1:1",
},
{
err: errors.New("test error"),
expectErr: true,
},
}
for _, test := range tests {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(tcFilterOutput), test.err },
func() ([]byte, error) { return []byte{}, test.err },
func() ([]byte, error) { return []byte{}, test.err },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
iface := "cbr0"
shaper := &tcShaper{e: &fexec, iface: iface}
if err := shaper.Reset(test.cidr); err != nil && !test.expectErr {
t.Errorf("unexpected error: %v", err)
return
} else if test.expectErr && err == nil {
t.Error("unexpected non-error")
return
}
// No more testing in the error case
if test.expectErr {
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("unexpected number of calls: %d, expected: 1", fcmd.CombinedOutputCalls)
}
return
}
if fcmd.CombinedOutputCalls != 3 {
t.Errorf("unexpected number of calls: %d, expected: 3", fcmd.CombinedOutputCalls)
}
for ix := range fcmd.CombinedOutputLog {
output := fcmd.CombinedOutputLog[ix]
if output[0] != "tc" {
t.Errorf("unexpected command: %s, expected tc", output[0])
}
if output[4] != iface {
t.Errorf("unexpected interface: %s, expected %s (%v)", output[4], iface, output)
}
if ix == 1 && output[12] != test.expectedHandle {
t.Errorf("unexpected handle: %s, expected: %s", output[12], test.expectedHandle)
}
if ix == 2 && output[8] != test.expectedClass {
t.Errorf("unexpected class: %s, expected: %s", output[8], test.expectedClass)
}
}
}
}
var tcQdisc = "qdisc htb 1: root refcnt 2 r2q 10 default 30 direct_packets_stat 0\n"
func TestReconcileInterfaceExists(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(tcQdisc), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
iface := "cbr0"
shaper := &tcShaper{e: &fexec, iface: iface}
err := shaper.ReconcileInterface()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("unexpected number of calls: %d", fcmd.CombinedOutputCalls)
}
output := fcmd.CombinedOutputLog[0]
if len(output) != 5 {
t.Errorf("unexpected command: %v", output)
}
if output[0] != "tc" {
t.Errorf("unexpected command: %s", output[0])
}
if output[4] != iface {
t.Errorf("unexpected interface: %s, expected %s", output[4], iface)
}
if output[2] != "show" {
t.Errorf("unexpected action: %s", output[2])
}
}
func testReconcileInterfaceHasNoData(t *testing.T, output string) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(output), nil },
func() ([]byte, error) { return []byte(output), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
iface := "cbr0"
shaper := &tcShaper{e: &fexec, iface: iface}
err := shaper.ReconcileInterface()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if fcmd.CombinedOutputCalls != 2 {
t.Errorf("unexpected number of calls: %d", fcmd.CombinedOutputCalls)
}
for ix, output := range fcmd.CombinedOutputLog {
if output[0] != "tc" {
t.Errorf("unexpected command: %s", output[0])
}
if output[4] != iface {
t.Errorf("unexpected interface: %s, expected %s", output[4], iface)
}
if ix == 0 {
if len(output) != 5 {
t.Errorf("unexpected command: %v", output)
}
if output[2] != "show" {
t.Errorf("unexpected action: %s", output[2])
}
}
if ix == 1 {
if len(output) != 11 {
t.Errorf("unexpected command: %v", output)
}
if output[2] != "add" {
t.Errorf("unexpected action: %s", output[2])
}
if output[7] != "1:" {
t.Errorf("unexpected root class: %s", output[7])
}
if output[8] != "htb" {
t.Errorf("unexpected qdisc algo: %s", output[8])
}
}
}
}
func TestReconcileInterfaceDoesntExist(t *testing.T) {
testReconcileInterfaceHasNoData(t, "\n")
}
var tcQdiscNoqueue = "qdisc noqueue 0: root refcnt 2 \n"
func TestReconcileInterfaceExistsWithNoqueue(t *testing.T) {
testReconcileInterfaceHasNoData(t, tcQdiscNoqueue)
}
var tcQdiscWrong = []string{
"qdisc htb 2: root refcnt 2 r2q 10 default 30 direct_packets_stat 0\n",
"qdisc foo 1: root refcnt 2 r2q 10 default 30 direct_packets_stat 0\n",
}
func TestReconcileInterfaceIsWrong(t *testing.T) {
for _, test := range tcQdiscWrong {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(test), nil },
func() ([]byte, error) { return []byte("\n"), nil },
func() ([]byte, error) { return []byte("\n"), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
iface := "cbr0"
shaper := &tcShaper{e: &fexec, iface: iface}
err := shaper.ReconcileInterface()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if fcmd.CombinedOutputCalls != 3 {
t.Errorf("unexpected number of calls: %d", fcmd.CombinedOutputCalls)
}
for ix, output := range fcmd.CombinedOutputLog {
if output[0] != "tc" {
t.Errorf("unexpected command: %s", output[0])
}
if output[4] != iface {
t.Errorf("unexpected interface: %s, expected %s", output[4], iface)
}
if ix == 0 {
if len(output) != 5 {
t.Errorf("unexpected command: %v", output)
}
if output[2] != "show" {
t.Errorf("unexpected action: %s", output[2])
}
}
if ix == 1 {
if len(output) != 8 {
t.Errorf("unexpected command: %v", output)
}
if output[2] != "delete" {
t.Errorf("unexpected action: %s", output[2])
}
if output[7] != strings.Split(test, " ")[2] {
t.Errorf("unexpected class: %s, expected: %s", output[7], strings.Split(test, " ")[2])
}
}
if ix == 2 {
if len(output) != 11 {
t.Errorf("unexpected command: %v", output)
}
if output[7] != "1:" {
t.Errorf("unexpected root class: %s", output[7])
}
if output[8] != "htb" {
t.Errorf("unexpected qdisc algo: %s", output[8])
}
}
}
}
}

View File

@ -1,52 +0,0 @@
// +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
}

View File

@ -1,65 +0,0 @@
/*
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
}

View File

@ -1,90 +0,0 @@
/*
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 (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
)
func TestExtractPodBandwidthResources(t *testing.T) {
four, _ := resource.ParseQuantity("4M")
ten, _ := resource.ParseQuantity("10M")
twenty, _ := resource.ParseQuantity("20M")
testPod := func(ingress, egress string) *api.Pod {
pod := &api.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}}
if len(ingress) != 0 {
pod.Annotations["kubernetes.io/ingress-bandwidth"] = ingress
}
if len(egress) != 0 {
pod.Annotations["kubernetes.io/egress-bandwidth"] = egress
}
return pod
}
tests := []struct {
pod *api.Pod
expectedIngress *resource.Quantity
expectedEgress *resource.Quantity
expectError bool
}{
{
pod: &api.Pod{},
},
{
pod: testPod("10M", ""),
expectedIngress: &ten,
},
{
pod: testPod("", "10M"),
expectedEgress: &ten,
},
{
pod: testPod("4M", "20M"),
expectedIngress: &four,
expectedEgress: &twenty,
},
{
pod: testPod("foo", ""),
expectError: true,
},
}
for _, test := range tests {
ingress, egress, err := ExtractPodBandwidthResources(test.pod.Annotations)
if test.expectError {
if err == nil {
t.Errorf("unexpected non-error")
}
continue
}
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
if !reflect.DeepEqual(ingress, test.expectedIngress) {
t.Errorf("expected: %v, saw: %v", ingress, test.expectedIngress)
}
if !reflect.DeepEqual(egress, test.expectedEgress) {
t.Errorf("expected: %v, saw: %v", egress, test.expectedEgress)
}
}
}

View File

@ -1,36 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"config.go",
"doc.go",
],
importpath = "k8s.io/kubernetes/pkg/util/config",
deps = ["//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["config_test.go"],
embed = [":go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,140 +0,0 @@
/*
Copyright 2014 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 config
import (
"sync"
"k8s.io/apimachinery/pkg/util/wait"
)
type Merger interface {
// Invoked when a change from a source is received. May also function as an incremental
// merger if you wish to consume changes incrementally. Must be reentrant when more than
// one source is defined.
Merge(source string, update interface{}) error
}
// MergeFunc implements the Merger interface
type MergeFunc func(source string, update interface{}) error
func (f MergeFunc) Merge(source string, update interface{}) error {
return f(source, update)
}
// Mux is a class for merging configuration from multiple sources. Changes are
// pushed via channels and sent to the merge function.
type Mux struct {
// Invoked when an update is sent to a source.
merger Merger
// Sources and their lock.
sourceLock sync.RWMutex
// Maps source names to channels
sources map[string]chan interface{}
}
// NewMux creates a new mux that can merge changes from multiple sources.
func NewMux(merger Merger) *Mux {
mux := &Mux{
sources: make(map[string]chan interface{}),
merger: merger,
}
return mux
}
// Channel returns a channel where a configuration source
// can send updates of new configurations. Multiple calls with the same
// source will return the same channel. This allows change and state based sources
// to use the same channel. Different source names however will be treated as a
// union.
func (m *Mux) Channel(source string) chan interface{} {
if len(source) == 0 {
panic("Channel given an empty name")
}
m.sourceLock.Lock()
defer m.sourceLock.Unlock()
channel, exists := m.sources[source]
if exists {
return channel
}
newChannel := make(chan interface{})
m.sources[source] = newChannel
go wait.Until(func() { m.listen(source, newChannel) }, 0, wait.NeverStop)
return newChannel
}
func (m *Mux) listen(source string, listenChannel <-chan interface{}) {
for update := range listenChannel {
m.merger.Merge(source, update)
}
}
// Accessor is an interface for retrieving the current merge state.
type Accessor interface {
// MergedState returns a representation of the current merge state.
// Must be reentrant when more than one source is defined.
MergedState() interface{}
}
// AccessorFunc implements the Accessor interface.
type AccessorFunc func() interface{}
func (f AccessorFunc) MergedState() interface{} {
return f()
}
type Listener interface {
// OnUpdate is invoked when a change is made to an object.
OnUpdate(instance interface{})
}
// ListenerFunc receives a representation of the change or object.
type ListenerFunc func(instance interface{})
func (f ListenerFunc) OnUpdate(instance interface{}) {
f(instance)
}
type Broadcaster struct {
// Listeners for changes and their lock.
listenerLock sync.RWMutex
listeners []Listener
}
// NewBroadcaster registers a set of listeners that support the Listener interface
// and notifies them all on changes.
func NewBroadcaster() *Broadcaster {
return &Broadcaster{}
}
// Add registers listener to receive updates of changes.
func (b *Broadcaster) Add(listener Listener) {
b.listenerLock.Lock()
defer b.listenerLock.Unlock()
b.listeners = append(b.listeners, listener)
}
// Notify notifies all listeners.
func (b *Broadcaster) Notify(instance interface{}) {
b.listenerLock.RLock()
listeners := b.listeners
b.listenerLock.RUnlock()
for _, listener := range listeners {
listener.OnUpdate(instance)
}
}

View File

@ -1,120 +0,0 @@
/*
Copyright 2014 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 config
import (
"reflect"
"testing"
)
func TestConfigurationChannels(t *testing.T) {
mux := NewMux(nil)
channelOne := mux.Channel("one")
if channelOne != mux.Channel("one") {
t.Error("Didn't get the same muxuration channel back with the same name")
}
channelTwo := mux.Channel("two")
if channelOne == channelTwo {
t.Error("Got back the same muxuration channel for different names")
}
}
type MergeMock struct {
source string
update interface{}
t *testing.T
}
func (m MergeMock) Merge(source string, update interface{}) error {
if m.source != source {
m.t.Errorf("Expected %s, Got %s", m.source, source)
}
if !reflect.DeepEqual(m.update, update) {
m.t.Errorf("Expected %s, Got %s", m.update, update)
}
return nil
}
func TestMergeInvoked(t *testing.T) {
merger := MergeMock{"one", "test", t}
mux := NewMux(&merger)
mux.Channel("one") <- "test"
}
func TestMergeFuncInvoked(t *testing.T) {
ch := make(chan bool)
mux := NewMux(MergeFunc(func(source string, update interface{}) error {
if source != "one" {
t.Errorf("Expected %s, Got %s", "one", source)
}
if update.(string) != "test" {
t.Errorf("Expected %s, Got %s", "test", update)
}
ch <- true
return nil
}))
mux.Channel("one") <- "test"
<-ch
}
func TestSimultaneousMerge(t *testing.T) {
ch := make(chan bool, 2)
mux := NewMux(MergeFunc(func(source string, update interface{}) error {
switch source {
case "one":
if update.(string) != "test" {
t.Errorf("Expected %s, Got %s", "test", update)
}
case "two":
if update.(string) != "test2" {
t.Errorf("Expected %s, Got %s", "test2", update)
}
default:
t.Errorf("Unexpected source, Got %s", update)
}
ch <- true
return nil
}))
source := mux.Channel("one")
source2 := mux.Channel("two")
source <- "test"
source2 <- "test2"
<-ch
<-ch
}
func TestBroadcaster(t *testing.T) {
b := NewBroadcaster()
b.Notify(struct{}{})
ch := make(chan bool, 2)
b.Add(ListenerFunc(func(object interface{}) {
if object != "test" {
t.Errorf("Expected %s, Got %s", "test", object)
}
ch <- true
}))
b.Add(ListenerFunc(func(object interface{}) {
if object != "test" {
t.Errorf("Expected %s, Got %s", "test", object)
}
ch <- true
}))
b.Notify("test")
<-ch
<-ch
}

View File

@ -1,20 +0,0 @@
/*
Copyright 2014 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 config provides utility objects for decoupling sources of configuration and the
// actual configuration state. Consumers must implement the Merger interface to unify
// the sources of change into an object.
package config // import "k8s.io/kubernetes/pkg/util/config"

View File

@ -1,32 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["configz.go"],
importpath = "k8s.io/kubernetes/pkg/util/configz",
)
go_test(
name = "go_default_test",
srcs = ["configz_test.go"],
embed = [":go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,90 +0,0 @@
/*
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 configz
import (
"encoding/json"
"fmt"
"net/http"
"sync"
)
var (
configsGuard sync.RWMutex
configs = map[string]*Config{}
)
type Config struct {
val interface{}
}
func InstallHandler(m mux) {
m.Handle("/configz", http.HandlerFunc(handle))
}
type mux interface {
Handle(string, http.Handler)
}
func New(name string) (*Config, error) {
configsGuard.Lock()
defer configsGuard.Unlock()
if _, found := configs[name]; found {
return nil, fmt.Errorf("register config %q twice", name)
}
newConfig := Config{}
configs[name] = &newConfig
return &newConfig, nil
}
func Delete(name string) {
configsGuard.Lock()
defer configsGuard.Unlock()
delete(configs, name)
}
func (v *Config) Set(val interface{}) {
configsGuard.Lock()
defer configsGuard.Unlock()
v.val = val
}
func (v *Config) MarshalJSON() ([]byte, error) {
return json.Marshal(v.val)
}
func handle(w http.ResponseWriter, r *http.Request) {
if err := write(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func write(w http.ResponseWriter) error {
var b []byte
var err error
func() {
configsGuard.RLock()
defer configsGuard.RUnlock()
b, err = json.Marshal(configs)
}()
if err != nil {
return fmt.Errorf("error marshaling json: %v", err)
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(b)
return err
}

View File

@ -1,80 +0,0 @@
/*
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 configz
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestConfigz(t *testing.T) {
v, err := New("testing")
if err != nil {
t.Fatalf("err: %v", err)
}
v.Set("blah")
s := httptest.NewServer(http.HandlerFunc(handle))
defer s.Close()
resp, err := http.Get(s.URL + "/configz")
if err != nil {
t.Fatalf("err: %v", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("err: %v", err)
}
if string(body) != `{"testing":"blah"}` {
t.Fatalf("unexpected output: %s", body)
}
v.Set("bing")
resp, err = http.Get(s.URL + "/configz")
if err != nil {
t.Fatalf("err: %v", err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("err: %v", err)
}
if string(body) != `{"testing":"bing"}` {
t.Fatalf("unexpected output: %s", body)
}
Delete("testing")
resp, err = http.Get(s.URL + "/configz")
if err != nil {
t.Fatalf("err: %v", err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("err: %v", err)
}
if string(body) != `{}` {
t.Fatalf("unexpected output: %s", body)
}
if resp.Header.Get("Content-Type") != "application/json" {
t.Fatalf("unexpected Content-Type: %s", resp.Header.Get("Content-Type"))
}
}

View File

@ -1,39 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["conntrack.go"],
importpath = "k8s.io/kubernetes/pkg/util/conntrack",
visibility = ["//visibility:public"],
deps = [
"//pkg/util/net:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["conntrack_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/util/net:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,109 +0,0 @@
/*
Copyright 2016 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 conntrack
import (
"fmt"
"strconv"
"strings"
"k8s.io/api/core/v1"
utilnet "k8s.io/kubernetes/pkg/util/net"
"k8s.io/utils/exec"
)
// Utilities for dealing with conntrack
// NoConnectionToDelete is the error string returned by conntrack when no matching connections are found
const NoConnectionToDelete = "0 flow entries have been deleted"
func protoStr(proto v1.Protocol) string {
return strings.ToLower(string(proto))
}
func parametersWithFamily(isIPv6 bool, parameters ...string) []string {
if isIPv6 {
parameters = append(parameters, "-f", "ipv6")
}
return parameters
}
// ClearEntriesForIP uses the conntrack tool to delete the conntrack entries
// for the UDP connections specified by the given service IP
func ClearEntriesForIP(execer exec.Interface, ip string, protocol v1.Protocol) error {
parameters := parametersWithFamily(utilnet.IsIPv6String(ip), "-D", "--orig-dst", ip, "-p", protoStr(protocol))
err := Exec(execer, parameters...)
if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) {
// TODO: Better handling for deletion failure. When failure occur, stale udp connection may not get flushed.
// These stale udp connection will keep black hole traffic. Making this a best effort operation for now, since it
// is expensive to baby-sit all udp connections to kubernetes services.
return fmt.Errorf("error deleting connection tracking state for UDP service IP: %s, error: %v", ip, err)
}
return nil
}
// Exec executes the conntrack tool using the given parameters
func Exec(execer exec.Interface, parameters ...string) error {
conntrackPath, err := execer.LookPath("conntrack")
if err != nil {
return fmt.Errorf("error looking for path of conntrack: %v", err)
}
output, err := execer.Command(conntrackPath, parameters...).CombinedOutput()
if err != nil {
return fmt.Errorf("conntrack command returned: %q, error message: %s", string(output), err)
}
return nil
}
// Exists returns true if conntrack binary is installed.
func Exists(execer exec.Interface) bool {
_, err := execer.LookPath("conntrack")
return err == nil
}
// ClearEntriesForPort uses the conntrack tool to delete the conntrack entries
// for connections specified by the port.
// When a packet arrives, it will not go through NAT table again, because it is not "the first" packet.
// The solution is clearing the conntrack. Known issues:
// https://github.com/docker/docker/issues/8795
// https://github.com/kubernetes/kubernetes/issues/31983
func ClearEntriesForPort(execer exec.Interface, port int, isIPv6 bool, protocol v1.Protocol) error {
if port <= 0 {
return fmt.Errorf("Wrong port number. The port number must be greater than zero")
}
parameters := parametersWithFamily(isIPv6, "-D", "-p", protoStr(protocol), "--dport", strconv.Itoa(port))
err := Exec(execer, parameters...)
if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) {
return fmt.Errorf("error deleting conntrack entries for UDP port: %d, error: %v", port, err)
}
return nil
}
// ClearEntriesForNAT uses the conntrack tool to delete the conntrack entries
// for connections specified by the {origin, dest} IP pair.
func ClearEntriesForNAT(execer exec.Interface, origin, dest string, protocol v1.Protocol) error {
parameters := parametersWithFamily(utilnet.IsIPv6String(origin), "-D", "--orig-dst", origin, "--dst-nat", dest,
"-p", protoStr(protocol))
err := Exec(execer, parameters...)
if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) {
// TODO: Better handling for deletion failure. When failure occur, stale udp connection may not get flushed.
// These stale udp connection will keep black hole traffic. Making this a best effort operation for now, since it
// is expensive to baby sit all udp connections to kubernetes services.
return fmt.Errorf("error deleting conntrack entries for UDP peer {%s, %s}, error: %v", origin, dest, err)
}
return nil
}

View File

@ -1,236 +0,0 @@
/*
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 conntrack
import (
"fmt"
"strings"
"testing"
"k8s.io/api/core/v1"
utilnet "k8s.io/kubernetes/pkg/util/net"
"k8s.io/utils/exec"
fakeexec "k8s.io/utils/exec/testing"
)
func familyParamStr(isIPv6 bool) string {
if isIPv6 {
return " -f ipv6"
}
return ""
}
func TestExecConntrackTool(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
func() ([]byte, error) {
return []byte(""), fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
}
testCases := [][]string{
{"-L", "-p", "udp"},
{"-D", "-p", "udp", "-d", "10.0.240.1"},
{"-D", "-p", "udp", "--orig-dst", "10.240.0.2", "--dst-nat", "10.0.10.2"},
}
expectErr := []bool{false, false, true}
for i := range testCases {
err := Exec(&fexec, testCases[i]...)
if expectErr[i] {
if err == nil {
t.Errorf("expected err, got %v", err)
}
} else {
if err != nil {
t.Errorf("expected success, got %v", err)
}
}
execCmd := strings.Join(fcmd.CombinedOutputLog[i], " ")
expectCmd := fmt.Sprintf("%s %s", "conntrack", strings.Join(testCases[i], " "))
if execCmd != expectCmd {
t.Errorf("expect execute command: %s, but got: %s", expectCmd, execCmd)
}
}
}
func TestClearUDPConntrackForIP(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
func() ([]byte, error) {
return []byte(""), fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
},
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
}
testCases := []struct {
name string
ip string
}{
{"IPv4 success", "10.240.0.3"},
{"IPv4 success", "10.240.0.5"},
{"IPv4 simulated error", "10.240.0.4"},
{"IPv6 success", "2001:db8::10"},
}
svcCount := 0
for _, tc := range testCases {
if err := ClearEntriesForIP(&fexec, tc.ip, v1.ProtocolUDP); err != nil {
t.Errorf("%s test case:, Unexpected error: %v", tc.name, err)
}
expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s -p udp", tc.ip) + familyParamStr(utilnet.IsIPv6String(tc.ip))
execCommand := strings.Join(fcmd.CombinedOutputLog[svcCount], " ")
if expectCommand != execCommand {
t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand)
}
svcCount++
}
if svcCount != fexec.CommandCalls {
t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls)
}
}
func TestClearUDPConntrackForPort(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
func() ([]byte, error) {
return []byte(""), fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
},
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
}
testCases := []struct {
name string
port int
isIPv6 bool
}{
{"IPv4, no error", 8080, false},
{"IPv4, simulated error", 9090, false},
{"IPv6, no error", 6666, true},
}
svcCount := 0
for _, tc := range testCases {
err := ClearEntriesForPort(&fexec, tc.port, tc.isIPv6, v1.ProtocolUDP)
if err != nil {
t.Errorf("%s test case: Unexpected error: %v", tc.name, err)
}
expectCommand := fmt.Sprintf("conntrack -D -p udp --dport %d", tc.port) + familyParamStr(tc.isIPv6)
execCommand := strings.Join(fcmd.CombinedOutputLog[svcCount], " ")
if expectCommand != execCommand {
t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand)
}
svcCount++
}
if svcCount != fexec.CommandCalls {
t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls)
}
}
func TestDeleteUDPConnections(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
func() ([]byte, error) {
return []byte(""), fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
},
func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
}
testCases := []struct {
name string
origin string
dest string
}{
{
name: "IPv4 success",
origin: "1.2.3.4",
dest: "10.20.30.40",
},
{
name: "IPv4 simulated failure",
origin: "2.3.4.5",
dest: "20.30.40.50",
},
{
name: "IPv6 success",
origin: "fd00::600d:f00d",
dest: "2001:db8::5",
},
}
svcCount := 0
for i, tc := range testCases {
err := ClearEntriesForNAT(&fexec, tc.origin, tc.dest, v1.ProtocolUDP)
if err != nil {
t.Errorf("%s test case: unexpected error: %v", tc.name, err)
}
expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s --dst-nat %s -p udp", tc.origin, tc.dest) + familyParamStr(utilnet.IsIPv6String(tc.origin))
execCommand := strings.Join(fcmd.CombinedOutputLog[i], " ")
if expectCommand != execCommand {
t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand)
}
svcCount++
}
if svcCount != fexec.CommandCalls {
t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls)
}
}

View File

@ -1,25 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"coverage_disabled.go",
"fake_test_deps.go",
],
importpath = "k8s.io/kubernetes/pkg/util/coverage",
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,8 +0,0 @@
approvers:
- bentheelder
- spiffxp
reviewers:
- bentheelder
- spiffxp
labels:
- sig/testing

View File

@ -1,91 +0,0 @@
// +build coverage
/*
Copyright 2018 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 coverage provides tools for coverage-instrumented binaries to collect and
// flush coverage information.
package coverage
import (
"flag"
"fmt"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog"
"os"
"testing"
"time"
)
var coverageFile string
// tempCoveragePath returns a temporary file to write coverage information to.
// The file is in the same directory as the destination, ensuring os.Rename will work.
func tempCoveragePath() string {
return coverageFile + ".tmp"
}
// InitCoverage is called from the dummy unit test to prepare Go's coverage framework.
// Clients should never need to call it.
func InitCoverage(name string) {
// We read the coverage destination in from the KUBE_COVERAGE_FILE env var,
// or if it's empty we just use a default in /tmp
coverageFile = os.Getenv("KUBE_COVERAGE_FILE")
if coverageFile == "" {
coverageFile = "/tmp/k8s-" + name + ".cov"
}
fmt.Println("Dumping coverage information to " + coverageFile)
flushInterval := 5 * time.Second
requestedInterval := os.Getenv("KUBE_COVERAGE_FLUSH_INTERVAL")
if requestedInterval != "" {
if duration, err := time.ParseDuration(requestedInterval); err == nil {
flushInterval = duration
} else {
panic("Invalid KUBE_COVERAGE_FLUSH_INTERVAL value; try something like '30s'.")
}
}
// Set up the unit test framework with the required arguments to activate test coverage.
flag.CommandLine.Parse([]string{"-test.coverprofile", tempCoveragePath()})
// Begin periodic logging
go wait.Forever(FlushCoverage, flushInterval)
}
// FlushCoverage flushes collected coverage information to disk.
// The destination file is configured at startup and cannot be changed.
// Calling this function also sends a line like "coverage: 5% of statements" to stdout.
func FlushCoverage() {
// We're not actually going to run any tests, but we need Go to think we did so it writes
// coverage information to disk. To achieve this, we create a bunch of empty test suites and
// have it "run" them.
tests := []testing.InternalTest{}
benchmarks := []testing.InternalBenchmark{}
examples := []testing.InternalExample{}
var deps fakeTestDeps
dummyRun := testing.MainStart(deps, tests, benchmarks, examples)
dummyRun.Run()
// Once it writes to the temporary path, we move it to the intended path.
// This gets us atomic updates from the perspective of another process trying to access
// the file.
if err := os.Rename(tempCoveragePath(), coverageFile); err != nil {
klog.Errorf("Couldn't move coverage file from %s to %s", coverageFile, tempCoveragePath())
}
}

View File

@ -1,29 +0,0 @@
// +build !coverage
/*
Copyright 2018 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 coverage
// InitCoverage is illegal when not running with coverage.
func InitCoverage(name string) {
panic("Called InitCoverage when not built with coverage instrumentation.")
}
// FlushCoverage is a no-op when not running with coverage.
func FlushCoverage() {
}

View File

@ -1,54 +0,0 @@
/*
Copyright 2018 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 coverage
import (
"io"
)
// This is an implementation of testing.testDeps. It doesn't need to do anything, because
// no tests are actually run. It does need a concrete implementation of at least ImportPath,
// which is called unconditionally when running tests.
type fakeTestDeps struct{}
func (fakeTestDeps) ImportPath() string {
return ""
}
func (fakeTestDeps) MatchString(pat, str string) (bool, error) {
return false, nil
}
func (fakeTestDeps) StartCPUProfile(io.Writer) error {
return nil
}
func (fakeTestDeps) StopCPUProfile() {}
func (fakeTestDeps) StartTestLog(io.Writer) {}
func (fakeTestDeps) StopTestLog() error {
return nil
}
func (fakeTestDeps) WriteHeapProfile(io.Writer) error {
return nil
}
func (fakeTestDeps) WriteProfileTo(string, io.Writer, int) error {
return nil
}

View File

@ -1,38 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dbus.go",
"doc.go",
"fake_dbus.go",
],
importpath = "k8s.io/kubernetes/pkg/util/dbus",
deps = ["//vendor/github.com/godbus/dbus:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["dbus_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/github.com/godbus/dbus:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,133 +0,0 @@
/*
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 dbus
import (
godbus "github.com/godbus/dbus"
)
// Interface is an interface that presents a subset of the godbus/dbus API. Use this
// when you want to inject fakeable/mockable D-Bus behavior.
type Interface interface {
// SystemBus returns a connection to the system bus, connecting to it
// first if necessary
SystemBus() (Connection, error)
// SessionBus returns a connection to the session bus, connecting to it
// first if necessary
SessionBus() (Connection, error)
}
// Connection represents a D-Bus connection
type Connection interface {
// Returns an Object representing the bus itself
BusObject() Object
// Object creates a representation of a remote D-Bus object
Object(name, path string) Object
// Signal registers or unregisters a channel to receive D-Bus signals
Signal(ch chan<- *godbus.Signal)
}
// Object represents a remote D-Bus object
type Object interface {
// Call synchronously calls a D-Bus method
Call(method string, flags godbus.Flags, args ...interface{}) Call
}
// Call represents a pending or completed D-Bus method call
type Call interface {
// Store returns a completed call's return values, or an error
Store(retvalues ...interface{}) error
}
// Implements Interface in terms of actually talking to D-Bus
type dbusImpl struct {
systemBus *connImpl
sessionBus *connImpl
}
// Implements Connection as a godbus.Conn
type connImpl struct {
conn *godbus.Conn
}
// Implements Object as a godbus.Object
type objectImpl struct {
object godbus.BusObject
}
// Implements Call as a godbus.Call
type callImpl struct {
call *godbus.Call
}
// New returns a new Interface which will use godbus to talk to D-Bus
func New() Interface {
return &dbusImpl{}
}
// SystemBus is part of Interface
func (db *dbusImpl) SystemBus() (Connection, error) {
if db.systemBus == nil {
bus, err := godbus.SystemBus()
if err != nil {
return nil, err
}
db.systemBus = &connImpl{bus}
}
return db.systemBus, nil
}
// SessionBus is part of Interface
func (db *dbusImpl) SessionBus() (Connection, error) {
if db.sessionBus == nil {
bus, err := godbus.SessionBus()
if err != nil {
return nil, err
}
db.sessionBus = &connImpl{bus}
}
return db.sessionBus, nil
}
// BusObject is part of the Connection interface
func (conn *connImpl) BusObject() Object {
return &objectImpl{conn.conn.BusObject()}
}
// Object is part of the Connection interface
func (conn *connImpl) Object(name, path string) Object {
return &objectImpl{conn.conn.Object(name, godbus.ObjectPath(path))}
}
// Signal is part of the Connection interface
func (conn *connImpl) Signal(ch chan<- *godbus.Signal) {
conn.conn.Signal(ch)
}
// Call is part of the Object interface
func (obj *objectImpl) Call(method string, flags godbus.Flags, args ...interface{}) Call {
return &callImpl{obj.object.Call(method, flags, args...)}
}
// Store is part of the Call interface
func (call *callImpl) Store(retvalues ...interface{}) error {
return call.call.Store(retvalues...)
}

View File

@ -1,243 +0,0 @@
/*
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 dbus
import (
"fmt"
"os"
"testing"
godbus "github.com/godbus/dbus"
)
const (
DBusNameFlagDoNotQueue uint32 = 1 << (iota + 1)
)
const (
DBusRequestNameReplyPrimaryOwner uint32 = iota + 1
DBusRequestNameReplyAlreadyOwner
)
const (
DBusReleaseNameReplyReleased uint32 = iota + 1
DBusReleaseNameReplyNotOwner
)
func doDBusTest(t *testing.T, dbus Interface, real bool) {
bus, err := dbus.SystemBus()
if err != nil {
if !real {
t.Errorf("dbus.SystemBus() failed with fake Interface")
}
t.Skipf("D-Bus is not running: %v", err)
}
busObj := bus.BusObject()
id := ""
err = busObj.Call("org.freedesktop.DBus.GetId", 0).Store(&id)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if len(id) == 0 {
t.Errorf("expected non-empty Id, got \"\"")
}
// Switch to the session bus for the rest, since the system bus is more
// locked down (and thus harder to trick into emitting signals).
bus, err = dbus.SessionBus()
if err != nil {
if !real {
t.Errorf("dbus.SystemBus() failed with fake Interface")
}
t.Skipf("D-Bus session bus is not available: %v", err)
}
busObj = bus.BusObject()
name := fmt.Sprintf("io.kubernetes.dbus_test_%d", os.Getpid())
owner := ""
err = busObj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&owner)
if err == nil {
t.Errorf("expected '%s' to be un-owned, but found owner %s", name, owner)
}
dbuserr, ok := err.(godbus.Error)
if !ok {
t.Errorf("expected godbus.Error, but got %#v", err)
}
if dbuserr.Name != "org.freedesktop.DBus.Error.NameHasNoOwner" {
t.Errorf("expected NameHasNoOwner error but got %v", err)
}
sigchan := make(chan *godbus.Signal, 10)
bus.Signal(sigchan)
rule := fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", name)
err = busObj.Call("org.freedesktop.DBus.AddMatch", 0, rule).Store()
if err != nil {
t.Errorf("expected success, got %v", err)
}
var ret uint32
err = busObj.Call("org.freedesktop.DBus.RequestName", 0, name, DBusNameFlagDoNotQueue).Store(&ret)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if ret != DBusRequestNameReplyPrimaryOwner {
t.Errorf("expected %v, got %v", DBusRequestNameReplyPrimaryOwner, ret)
}
err = busObj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&owner)
if err != nil {
t.Errorf("expected success, got %v", err)
}
var changedSignal, acquiredSignal, lostSignal *godbus.Signal
sig1 := <-sigchan
sig2 := <-sigchan
// We get two signals, but the order isn't guaranteed
if sig1.Name == "org.freedesktop.DBus.NameOwnerChanged" {
changedSignal = sig1
acquiredSignal = sig2
} else {
acquiredSignal = sig1
changedSignal = sig2
}
if acquiredSignal.Sender != "org.freedesktop.DBus" || acquiredSignal.Name != "org.freedesktop.DBus.NameAcquired" {
t.Errorf("expected NameAcquired signal, got %v", acquiredSignal)
}
acquiredName := acquiredSignal.Body[0].(string)
if acquiredName != name {
t.Errorf("unexpected NameAcquired arguments: %v", acquiredSignal)
}
if changedSignal.Sender != "org.freedesktop.DBus" || changedSignal.Name != "org.freedesktop.DBus.NameOwnerChanged" {
t.Errorf("expected NameOwnerChanged signal, got %v", changedSignal)
}
changedName := changedSignal.Body[0].(string)
oldOwner := changedSignal.Body[1].(string)
newOwner := changedSignal.Body[2].(string)
if changedName != name || oldOwner != "" || newOwner != owner {
t.Errorf("unexpected NameOwnerChanged arguments: %v", changedSignal)
}
err = busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&ret)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if ret != DBusReleaseNameReplyReleased {
t.Errorf("expected %v, got %v", DBusReleaseNameReplyReleased, ret)
}
sig1 = <-sigchan
sig2 = <-sigchan
if sig1.Name == "org.freedesktop.DBus.NameOwnerChanged" {
changedSignal = sig1
lostSignal = sig2
} else {
lostSignal = sig1
changedSignal = sig2
}
if lostSignal.Sender != "org.freedesktop.DBus" || lostSignal.Name != "org.freedesktop.DBus.NameLost" {
t.Errorf("expected NameLost signal, got %v", lostSignal)
}
lostName := lostSignal.Body[0].(string)
if lostName != name {
t.Errorf("unexpected NameLost arguments: %v", lostSignal)
}
if changedSignal.Sender != "org.freedesktop.DBus" || changedSignal.Name != "org.freedesktop.DBus.NameOwnerChanged" {
t.Errorf("expected NameOwnerChanged signal, got %v", changedSignal)
}
changedName = changedSignal.Body[0].(string)
oldOwner = changedSignal.Body[1].(string)
newOwner = changedSignal.Body[2].(string)
if changedName != name || oldOwner != owner || newOwner != "" {
t.Errorf("unexpected NameOwnerChanged arguments: %v", changedSignal)
}
if len(sigchan) != 0 {
t.Errorf("unexpected extra signals (%d)", len(sigchan))
}
// Unregister sigchan
bus.Signal(sigchan)
}
func TestRealDBus(t *testing.T) {
dbus := New()
doDBusTest(t, dbus, true)
}
func TestFakeDBus(t *testing.T) {
uniqueName := ":1.1"
ownedName := ""
fakeSystem := NewFakeConnection()
fakeSystem.SetBusObject(
func(method string, args ...interface{}) ([]interface{}, error) {
if method == "org.freedesktop.DBus.GetId" {
return []interface{}{"foo"}, nil
}
return nil, fmt.Errorf("unexpected method call '%s'", method)
},
)
fakeSession := NewFakeConnection()
fakeSession.SetBusObject(
func(method string, args ...interface{}) ([]interface{}, error) {
if method == "org.freedesktop.DBus.GetNameOwner" {
checkName := args[0].(string)
if checkName != ownedName {
return nil, godbus.Error{Name: "org.freedesktop.DBus.Error.NameHasNoOwner", Body: nil}
}
return []interface{}{uniqueName}, nil
} else if method == "org.freedesktop.DBus.RequestName" {
reqName := args[0].(string)
_ = args[1].(uint32)
if ownedName != "" {
return []interface{}{DBusRequestNameReplyAlreadyOwner}, nil
}
ownedName = reqName
fakeSession.EmitSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", reqName)
fakeSession.EmitSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", reqName, "", uniqueName)
return []interface{}{DBusRequestNameReplyPrimaryOwner}, nil
} else if method == "org.freedesktop.DBus.ReleaseName" {
reqName := args[0].(string)
if reqName != ownedName {
return []interface{}{DBusReleaseNameReplyNotOwner}, nil
}
ownedName = ""
fakeSession.EmitSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", reqName, uniqueName, "")
fakeSession.EmitSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameLost", reqName)
return []interface{}{DBusReleaseNameReplyReleased}, nil
} else if method == "org.freedesktop.DBus.AddMatch" {
return nil, nil
} else {
return nil, fmt.Errorf("unexpected method call '%s'", method)
}
},
)
dbus := NewFake(fakeSystem, fakeSession)
doDBusTest(t, dbus, false)
}

View File

@ -1,18 +0,0 @@
/*
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 dbus provides an injectable interface and implementations for D-Bus communication
package dbus // import "k8s.io/kubernetes/pkg/util/dbus"

View File

@ -1,140 +0,0 @@
/*
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 dbus
import (
"fmt"
"sync"
godbus "github.com/godbus/dbus"
)
// Fake is a simple fake Interface type.
type Fake struct {
systemBus *FakeConnection
sessionBus *FakeConnection
}
// FakeConnection represents a fake D-Bus connection
type FakeConnection struct {
lock sync.Mutex
busObject *fakeObject
objects map[string]*fakeObject
signalHandlers []chan<- *godbus.Signal
}
// FakeHandler is used to handle fake D-Bus method calls
type FakeHandler func(method string, args ...interface{}) ([]interface{}, error)
type fakeObject struct {
handler FakeHandler
}
type fakeCall struct {
ret []interface{}
err error
}
// NewFake returns a new Interface which will fake talking to D-Bus
func NewFake(systemBus *FakeConnection, sessionBus *FakeConnection) *Fake {
return &Fake{systemBus, sessionBus}
}
// NewFakeConnection returns a FakeConnection Interface
func NewFakeConnection() *FakeConnection {
return &FakeConnection{
objects: make(map[string]*fakeObject),
}
}
// SystemBus is part of Interface
func (db *Fake) SystemBus() (Connection, error) {
if db.systemBus != nil {
return db.systemBus, nil
}
return nil, fmt.Errorf("DBus is not running")
}
// SessionBus is part of Interface
func (db *Fake) SessionBus() (Connection, error) {
if db.sessionBus != nil {
return db.sessionBus, nil
}
return nil, fmt.Errorf("DBus is not running")
}
// BusObject is part of the Connection interface
func (conn *FakeConnection) BusObject() Object {
return conn.busObject
}
// Object is part of the Connection interface
func (conn *FakeConnection) Object(name, path string) Object {
return conn.objects[name+path]
}
// Signal is part of the Connection interface
func (conn *FakeConnection) Signal(ch chan<- *godbus.Signal) {
conn.lock.Lock()
defer conn.lock.Unlock()
for i := range conn.signalHandlers {
if conn.signalHandlers[i] == ch {
conn.signalHandlers = append(conn.signalHandlers[:i], conn.signalHandlers[i+1:]...)
return
}
}
conn.signalHandlers = append(conn.signalHandlers, ch)
}
// SetBusObject sets the handler for the BusObject of conn
func (conn *FakeConnection) SetBusObject(handler FakeHandler) {
conn.busObject = &fakeObject{handler}
}
// AddObject adds a handler for the Object at name and path
func (conn *FakeConnection) AddObject(name, path string, handler FakeHandler) {
conn.objects[name+path] = &fakeObject{handler}
}
// EmitSignal emits a signal on conn
func (conn *FakeConnection) EmitSignal(name, path, iface, signal string, args ...interface{}) {
conn.lock.Lock()
defer conn.lock.Unlock()
sig := &godbus.Signal{
Sender: name,
Path: godbus.ObjectPath(path),
Name: iface + "." + signal,
Body: args,
}
for _, ch := range conn.signalHandlers {
ch <- sig
}
}
// Call is part of the Object interface
func (obj *fakeObject) Call(method string, flags godbus.Flags, args ...interface{}) Call {
ret, err := obj.handler(method, args...)
return &fakeCall{ret, err}
}
// Store is part of the Call interface
func (call *fakeCall) Store(retvalues ...interface{}) error {
if call.err != nil {
return call.err
}
return godbus.Store(call.ret, retvalues...)
}

View File

@ -1,37 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["ebtables.go"],
importpath = "k8s.io/kubernetes/pkg/util/ebtables",
deps = ["//vendor/k8s.io/utils/exec:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["ebtables_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,190 +0,0 @@
/*
Copyright 2016 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 ebtables
import (
"fmt"
"regexp"
"strings"
"sync"
utilexec "k8s.io/utils/exec"
)
const (
cmdebtables = "ebtables"
// Flag to show full mac in output. The default representation omits leading zeroes.
fullMac = "--Lmac2"
)
type RulePosition string
const (
Prepend RulePosition = "-I"
Append RulePosition = "-A"
)
type Table string
const (
TableNAT Table = "nat"
TableFilter Table = "filter"
)
type Chain string
const (
ChainPostrouting Chain = "POSTROUTING"
ChainPrerouting Chain = "PREROUTING"
ChainOutput Chain = "OUTPUT"
ChainInput Chain = "INPUT"
)
type operation string
const (
opCreateChain operation = "-N"
opFlushChain operation = "-F"
opDeleteChain operation = "-X"
opListChain operation = "-L"
opAppendRule operation = "-A"
opPrependRule operation = "-I"
opDeleteRule operation = "-D"
)
// An injectable interface for running ebtables commands. Implementations must be goroutine-safe.
type Interface interface {
// GetVersion returns the "X.Y.Z" semver string for ebtables.
GetVersion() (string, error)
// EnsureRule checks if the specified rule is present and, if not, creates it. If the rule existed, return true.
// WARNING: ebtables does not provide check operation like iptables do. Hence we have to do a string match of args.
// Input args must follow the format and sequence of ebtables list output. Otherwise, EnsureRule will always create
// new rules and causing duplicates.
EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error)
// EnsureChain checks if the specified chain is present and, if not, creates it. If the rule existed, return true.
EnsureChain(table Table, chain Chain) (bool, error)
// DeleteChain deletes the specified chain. If the chain did not exist, return error.
DeleteChain(table Table, chain Chain) error
// FlushChain flush the specified chain. If the chain did not exist, return error.
FlushChain(table Table, chain Chain) error
}
// runner implements Interface in terms of exec("ebtables").
type runner struct {
mu sync.Mutex
exec utilexec.Interface
}
// New returns a new Interface which will exec ebtables.
func New(exec utilexec.Interface) Interface {
runner := &runner{
exec: exec,
}
return runner
}
func makeFullArgs(table Table, op operation, chain Chain, args ...string) []string {
return append([]string{"-t", string(table), string(op), string(chain)}, args...)
}
// getEbtablesVersionString runs "ebtables --version" to get the version string
// in the form "X.X.X"
func getEbtablesVersionString(exec utilexec.Interface) (string, error) {
// this doesn't access mutable state so we don't need to use the interface / runner
bytes, err := exec.Command(cmdebtables, "--version").CombinedOutput()
if err != nil {
return "", err
}
versionMatcher := regexp.MustCompile("v([0-9]+\\.[0-9]+\\.[0-9]+)")
match := versionMatcher.FindStringSubmatch(string(bytes))
if match == nil {
return "", fmt.Errorf("no ebtables version found in string: %s", bytes)
}
return match[1], nil
}
func (runner *runner) GetVersion() (string, error) {
return getEbtablesVersionString(runner.exec)
}
func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) {
exist := true
fullArgs := makeFullArgs(table, opListChain, chain, fullMac)
out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput()
if err != nil {
exist = false
} else {
exist = checkIfRuleExists(string(out), args...)
}
if !exist {
fullArgs = makeFullArgs(table, operation(position), chain, args...)
out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput()
if err != nil {
return exist, fmt.Errorf("Failed to ensure rule: %v, output: %v", err, string(out))
}
}
return exist, nil
}
func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) {
exist := true
args := makeFullArgs(table, opListChain, chain)
_, err := runner.exec.Command(cmdebtables, args...).CombinedOutput()
if err != nil {
exist = false
}
if !exist {
args = makeFullArgs(table, opCreateChain, chain)
out, err := runner.exec.Command(cmdebtables, args...).CombinedOutput()
if err != nil {
return exist, fmt.Errorf("Failed to ensure %v chain: %v, output: %v", chain, err, string(out))
}
}
return exist, nil
}
// checkIfRuleExists takes the output of ebtables list chain and checks if the input rules exists
// WARNING: checkIfRuleExists expects the input args matches the format and sequence of ebtables list output
func checkIfRuleExists(listChainOutput string, args ...string) bool {
rule := strings.Join(args, " ")
for _, line := range strings.Split(listChainOutput, "\n") {
if strings.TrimSpace(line) == rule {
return true
}
}
return false
}
func (runner *runner) DeleteChain(table Table, chain Chain) error {
fullArgs := makeFullArgs(table, opDeleteChain, chain)
out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to delete %v chain %v: %v, output: %v", string(table), string(chain), err, string(out))
}
return nil
}
func (runner *runner) FlushChain(table Table, chain Chain) error {
fullArgs := makeFullArgs(table, opFlushChain, chain)
out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to flush %v chain %v: %v, output: %v", string(table), string(chain), err, string(out))
}
return nil
}

View File

@ -1,126 +0,0 @@
/*
Copyright 2016 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 ebtables
import (
"strings"
"testing"
"k8s.io/utils/exec"
fakeexec "k8s.io/utils/exec/testing"
)
func TestEnsureChain(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Does not Exists
func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 1} },
// Success
func() ([]byte, error) { return []byte{}, nil },
// Exists
func() ([]byte, error) { return nil, nil },
// Does not Exists
func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 1} },
// Fail to create chain
func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 2} },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
exists, err := runner.EnsureChain(TableFilter, "TEST-CHAIN")
if exists {
t.Errorf("expected exists = false")
}
if err != nil {
t.Errorf("expected err = nil")
}
exists, err = runner.EnsureChain(TableFilter, "TEST-CHAIN")
if !exists {
t.Errorf("expected exists = true")
}
if err != nil {
t.Errorf("expected err = nil")
}
exists, err = runner.EnsureChain(TableFilter, "TEST-CHAIN")
if exists {
t.Errorf("expected exists = false")
}
errStr := "Failed to ensure TEST-CHAIN chain: exit 2, output:"
if err == nil || !strings.Contains(err.Error(), errStr) {
t.Errorf("expected error: %q", errStr)
}
}
func TestEnsureRule(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Exists
func() ([]byte, error) {
return []byte(`Bridge table: filter
Bridge chain: OUTPUT, entries: 4, policy: ACCEPT
-j TEST
`), nil
},
// Does not Exists.
func() ([]byte, error) {
return []byte(`Bridge table: filter
Bridge chain: TEST, entries: 0, policy: ACCEPT`), nil
},
// Fail to create
func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 2} },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
exists, err := runner.EnsureRule(Append, TableFilter, ChainOutput, "-j", "TEST")
if !exists {
t.Errorf("expected exists = true")
}
if err != nil {
t.Errorf("expected err = nil")
}
exists, err = runner.EnsureRule(Append, TableFilter, ChainOutput, "-j", "NEXT-TEST")
if exists {
t.Errorf("expected exists = false")
}
errStr := "Failed to ensure rule: exit 2, output: "
if err == nil || err.Error() != errStr {
t.Errorf("expected error: %q", errStr)
}
}

View File

@ -1,33 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["env.go"],
importpath = "k8s.io/kubernetes/pkg/util/env",
)
go_test(
name = "go_default_test",
srcs = ["env_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,51 +0,0 @@
/*
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 env
import (
"os"
"strconv"
)
func GetEnvAsStringOrFallback(key, defaultValue string) string {
if v := os.Getenv(key); v != "" {
return v
}
return defaultValue
}
func GetEnvAsIntOrFallback(key string, defaultValue int) (int, error) {
if v := os.Getenv(key); v != "" {
value, err := strconv.Atoi(v)
if err != nil {
return defaultValue, err
}
return value, nil
}
return defaultValue, nil
}
func GetEnvAsFloat64OrFallback(key string, defaultValue float64) (float64, error) {
if v := os.Getenv(key); v != "" {
value, err := strconv.ParseFloat(v, 64)
if err != nil {
return defaultValue, err
}
return value, nil
}
return defaultValue, nil
}

View File

@ -1,82 +0,0 @@
/*
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 env
import (
"os"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetEnvAsStringOrFallback(t *testing.T) {
const expected = "foo"
assert := assert.New(t)
key := "FLOCKER_SET_VAR"
os.Setenv(key, expected)
assert.Equal(expected, GetEnvAsStringOrFallback(key, "~"+expected))
key = "FLOCKER_UNSET_VAR"
assert.Equal(expected, GetEnvAsStringOrFallback(key, expected))
}
func TestGetEnvAsIntOrFallback(t *testing.T) {
const expected = 1
assert := assert.New(t)
key := "FLOCKER_SET_VAR"
os.Setenv(key, strconv.Itoa(expected))
returnVal, _ := GetEnvAsIntOrFallback(key, 1)
assert.Equal(expected, returnVal)
key = "FLOCKER_UNSET_VAR"
returnVal, _ = GetEnvAsIntOrFallback(key, expected)
assert.Equal(expected, returnVal)
key = "FLOCKER_SET_VAR"
os.Setenv(key, "not-an-int")
returnVal, err := GetEnvAsIntOrFallback(key, 1)
assert.Equal(expected, returnVal)
if err == nil {
t.Error("expected error")
}
}
func TestGetEnvAsFloat64OrFallback(t *testing.T) {
const expected = 1.0
assert := assert.New(t)
key := "FLOCKER_SET_VAR"
os.Setenv(key, "1.0")
returnVal, _ := GetEnvAsFloat64OrFallback(key, 2.0)
assert.Equal(expected, returnVal)
key = "FLOCKER_UNSET_VAR"
returnVal, _ = GetEnvAsFloat64OrFallback(key, 1.0)
assert.Equal(expected, returnVal)
key = "FLOCKER_SET_VAR"
os.Setenv(key, "not-a-float")
returnVal, err := GetEnvAsFloat64OrFallback(key, 1.0)
assert.Equal(expected, returnVal)
assert.EqualError(err, "strconv.ParseFloat: parsing \"not-a-float\": invalid syntax")
}

View File

@ -1,36 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["file.go"],
importpath = "k8s.io/kubernetes/pkg/util/file",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["file_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/github.com/spf13/afero:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)

View File

@ -1,149 +0,0 @@
/*
Copyright 2017 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 file
import (
"os"
"path/filepath"
"sort"
"testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
)
func RecoverEnv(wd, tmpDir string) {
os.Chdir(wd)
os.RemoveAll(tmpDir)
}
func TestFileUtils(t *testing.T) {
fs := &afero.Afero{Fs: afero.NewOsFs()}
// Create tmp dir
tmpDir, err := fs.TempDir(os.TempDir(), "util_file_test_")
if err != nil {
t.Fatal("Failed to test: failed to create temp dir.")
}
// create tmp file
tmpFile, err := fs.TempFile(tmpDir, "test_file_exists_")
if err != nil {
t.Fatal("Failed to test: failed to create temp file.")
}
// create tmp sym link
tmpSymlinkName := filepath.Join(tmpDir, "test_file_exists_sym_link")
err = os.Symlink(tmpFile.Name(), tmpSymlinkName)
if err != nil {
t.Fatal("Failed to test: failed to create sym link.")
}
// create tmp sub dir
tmpSubDir, err := fs.TempDir(tmpDir, "sub_")
if err != nil {
t.Fatal("Failed to test: failed to create temp sub dir.")
}
// record the current dir
currentDir, err := os.Getwd()
if err != nil {
t.Fatal("Failed to test: failed to get current dir.")
}
// change the work dir to temp dir
err = os.Chdir(tmpDir)
if err != nil {
t.Fatal("Failed to test: failed to change work dir.")
}
// recover test environment
defer RecoverEnv(currentDir, tmpDir)
t.Run("TestFileExists", func(t *testing.T) {
tests := []struct {
name string
fileName string
expectedError bool
expectedValue bool
}{
{"file_not_exists", filepath.Join(tmpDir, "file_not_exist_case"), false, false},
{"file_exists", tmpFile.Name(), false, true},
}
for _, test := range tests {
realValued, realError := FileExists(test.fileName)
if test.expectedError {
assert.Errorf(t, realError, "Failed to test with '%s': %s", test.fileName, test.name)
} else {
assert.EqualValuesf(t, test.expectedValue, realValued, "Failed to test with '%s': %s", test.fileName, test.name)
}
}
})
t.Run("TestFileOrSymlinkExists", func(t *testing.T) {
tests := []struct {
name string
fileName string
expectedError bool
expectedValue bool
}{
{"file_not_exists", filepath.Join(tmpDir, "file_not_exist_case"), false, false},
{"file_exists", tmpFile.Name(), false, true},
{"symlink_exists", tmpSymlinkName, false, true},
}
for _, test := range tests {
realValued, realError := FileOrSymlinkExists(test.fileName)
if test.expectedError {
assert.Errorf(t, realError, "Failed to test with '%s': %s", test.fileName, test.name)
} else {
assert.EqualValuesf(t, test.expectedValue, realValued, "Failed to test with '%s': %s", test.fileName, test.name)
}
}
})
t.Run("TestReadDirNoStat", func(t *testing.T) {
_, tmpFileSimpleName := filepath.Split(tmpFile.Name())
_, tmpSymlinkSimpleName := filepath.Split(tmpSymlinkName)
_, tmpSubDirSimpleName := filepath.Split(tmpSubDir)
tests := []struct {
name string
dirName string
expectedError bool
expectedValue []string
}{
{"dir_not_exists", filepath.Join(tmpDir, "file_not_exist_case"), true, []string{}},
{"dir_is_empty", "", false, []string{tmpFileSimpleName, tmpSymlinkSimpleName, tmpSubDirSimpleName}},
{"dir_exists", tmpDir, false, []string{tmpFileSimpleName, tmpSymlinkSimpleName, tmpSubDirSimpleName}},
}
for _, test := range tests {
realValued, realError := ReadDirNoStat(test.dirName)
// execute sort action before compare
sort.Strings(realValued)
sort.Strings(test.expectedValue)
if test.expectedError {
assert.Errorf(t, realError, "Failed to test with '%s': %s", test.dirName, test.name)
} else {
assert.EqualValuesf(t, test.expectedValue, realValued, "Failed to test with '%s': %s", test.dirName, test.name)
}
}
})
}

View File

@ -1,34 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"defaultfs.go",
"fakefs.go",
"filesystem.go",
"watcher.go",
],
importpath = "k8s.io/kubernetes/pkg/util/filesystem",
deps = [
"//vendor/github.com/fsnotify/fsnotify:go_default_library",
"//vendor/github.com/spf13/afero:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,122 +0,0 @@
/*
Copyright 2017 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 filesystem
import (
"io/ioutil"
"os"
"path/filepath"
"time"
)
// DefaultFs implements Filesystem using same-named functions from "os" and "io/ioutil"
type DefaultFs struct{}
var _ Filesystem = DefaultFs{}
// Stat via os.Stat
func (DefaultFs) Stat(name string) (os.FileInfo, error) {
return os.Stat(name)
}
// Create via os.Create
func (DefaultFs) Create(name string) (File, error) {
file, err := os.Create(name)
if err != nil {
return nil, err
}
return &defaultFile{file}, nil
}
// Rename via os.Rename
func (DefaultFs) Rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
// MkdirAll via os.MkdirAll
func (DefaultFs) MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
// Chtimes via os.Chtimes
func (DefaultFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
return os.Chtimes(name, atime, mtime)
}
// RemoveAll via os.RemoveAll
func (DefaultFs) RemoveAll(path string) error {
return os.RemoveAll(path)
}
// Remove via os.RemoveAll
func (DefaultFs) Remove(name string) error {
return os.Remove(name)
}
// ReadFile via ioutil.ReadFile
func (DefaultFs) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
// TempDir via ioutil.TempDir
func (DefaultFs) TempDir(dir, prefix string) (string, error) {
return ioutil.TempDir(dir, prefix)
}
// TempFile via ioutil.TempFile
func (DefaultFs) TempFile(dir, prefix string) (File, error) {
file, err := ioutil.TempFile(dir, prefix)
if err != nil {
return nil, err
}
return &defaultFile{file}, nil
}
// ReadDir via ioutil.ReadDir
func (DefaultFs) ReadDir(dirname string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dirname)
}
// Walk via filepath.Walk
func (DefaultFs) Walk(root string, walkFn filepath.WalkFunc) error {
return filepath.Walk(root, walkFn)
}
// defaultFile implements File using same-named functions from "os"
type defaultFile struct {
file *os.File
}
// Name via os.File.Name
func (file *defaultFile) Name() string {
return file.file.Name()
}
// Write via os.File.Write
func (file *defaultFile) Write(b []byte) (n int, err error) {
return file.file.Write(b)
}
// Sync via os.File.Sync
func (file *defaultFile) Sync() error {
return file.file.Sync()
}
// Close via os.File.Close
func (file *defaultFile) Close() error {
return file.file.Close()
}

View File

@ -1,128 +0,0 @@
/*
Copyright 2017 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 filesystem
import (
"os"
"path/filepath"
"time"
"github.com/spf13/afero"
)
// fakeFs is implemented in terms of afero
type fakeFs struct {
a afero.Afero
}
// NewFakeFs returns a fake Filesystem that exists in-memory, useful for unit tests
func NewFakeFs() Filesystem {
return &fakeFs{a: afero.Afero{Fs: afero.NewMemMapFs()}}
}
// Stat via afero.Fs.Stat
func (fs *fakeFs) Stat(name string) (os.FileInfo, error) {
return fs.a.Fs.Stat(name)
}
// Create via afero.Fs.Create
func (fs *fakeFs) Create(name string) (File, error) {
file, err := fs.a.Fs.Create(name)
if err != nil {
return nil, err
}
return &fakeFile{file}, nil
}
// Rename via afero.Fs.Rename
func (fs *fakeFs) Rename(oldpath, newpath string) error {
return fs.a.Fs.Rename(oldpath, newpath)
}
// MkdirAll via afero.Fs.MkdirAll
func (fs *fakeFs) MkdirAll(path string, perm os.FileMode) error {
return fs.a.Fs.MkdirAll(path, perm)
}
// Chtimes via afero.Fs.Chtimes
func (fs *fakeFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
return fs.a.Fs.Chtimes(name, atime, mtime)
}
// ReadFile via afero.ReadFile
func (fs *fakeFs) ReadFile(filename string) ([]byte, error) {
return fs.a.ReadFile(filename)
}
// TempDir via afero.TempDir
func (fs *fakeFs) TempDir(dir, prefix string) (string, error) {
return fs.a.TempDir(dir, prefix)
}
// TempFile via afero.TempFile
func (fs *fakeFs) TempFile(dir, prefix string) (File, error) {
file, err := fs.a.TempFile(dir, prefix)
if err != nil {
return nil, err
}
return &fakeFile{file}, nil
}
// ReadDir via afero.ReadDir
func (fs *fakeFs) ReadDir(dirname string) ([]os.FileInfo, error) {
return fs.a.ReadDir(dirname)
}
// Walk via afero.Walk
func (fs *fakeFs) Walk(root string, walkFn filepath.WalkFunc) error {
return fs.a.Walk(root, walkFn)
}
// RemoveAll via afero.RemoveAll
func (fs *fakeFs) RemoveAll(path string) error {
return fs.a.RemoveAll(path)
}
// Remove via afero.RemoveAll
func (fs *fakeFs) Remove(name string) error {
return fs.a.Remove(name)
}
// fakeFile implements File; for use with fakeFs
type fakeFile struct {
file afero.File
}
// Name via afero.File.Name
func (file *fakeFile) Name() string {
return file.file.Name()
}
// Write via afero.File.Write
func (file *fakeFile) Write(b []byte) (n int, err error) {
return file.file.Write(b)
}
// Sync via afero.File.Sync
func (file *fakeFile) Sync() error {
return file.file.Sync()
}
// Close via afero.File.Close
func (file *fakeFile) Close() error {
return file.file.Close()
}

View File

@ -1,52 +0,0 @@
/*
Copyright 2017 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 filesystem
import (
"os"
"path/filepath"
"time"
)
// Filesystem is an interface that we can use to mock various filesystem operations
type Filesystem interface {
// from "os"
Stat(name string) (os.FileInfo, error)
Create(name string) (File, error)
Rename(oldpath, newpath string) error
MkdirAll(path string, perm os.FileMode) error
Chtimes(name string, atime time.Time, mtime time.Time) error
RemoveAll(path string) error
Remove(name string) error
// from "io/ioutil"
ReadFile(filename string) ([]byte, error)
TempDir(dir, prefix string) (string, error)
TempFile(dir, prefix string) (File, error)
ReadDir(dirname string) ([]os.FileInfo, error)
Walk(root string, walkFn filepath.WalkFunc) error
}
// File is an interface that we can use to mock various filesystem operations typically
// accessed through the File object from the "os" package
type File interface {
// for now, the only os.File methods used are those below, add more as necessary
Name() string
Write(b []byte) (n int, err error)
Sync() error
Close() error
}

View File

@ -1,89 +0,0 @@
/*
Copyright 2017 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 filesystem
import (
"github.com/fsnotify/fsnotify"
)
// FSWatcher is a callback-based filesystem watcher abstraction for fsnotify.
type FSWatcher interface {
// Initializes the watcher with the given watch handlers.
// Called before all other methods.
Init(FSEventHandler, FSErrorHandler) error
// Starts listening for events and errors.
// When an event or error occurs, the corresponding handler is called.
Run()
// Add a filesystem path to watch
AddWatch(path string) error
}
// FSEventHandler is called when a fsnotify event occurs.
type FSEventHandler func(event fsnotify.Event)
// FSErrorHandler is called when a fsnotify error occurs.
type FSErrorHandler func(err error)
type fsnotifyWatcher struct {
watcher *fsnotify.Watcher
eventHandler FSEventHandler
errorHandler FSErrorHandler
}
var _ FSWatcher = &fsnotifyWatcher{}
// NewFsnotifyWatcher returns an implementation of FSWatcher that continuously listens for
// fsnotify events and calls the event handler as soon as an event is received.
func NewFsnotifyWatcher() FSWatcher {
return &fsnotifyWatcher{}
}
func (w *fsnotifyWatcher) AddWatch(path string) error {
return w.watcher.Add(path)
}
func (w *fsnotifyWatcher) Init(eventHandler FSEventHandler, errorHandler FSErrorHandler) error {
var err error
w.watcher, err = fsnotify.NewWatcher()
if err != nil {
return err
}
w.eventHandler = eventHandler
w.errorHandler = errorHandler
return nil
}
func (w *fsnotifyWatcher) Run() {
go func() {
defer w.watcher.Close()
for {
select {
case event := <-w.watcher.Events:
if w.eventHandler != nil {
w.eventHandler(event)
}
case err := <-w.watcher.Errors:
if w.errorHandler != nil {
w.errorHandler(err)
}
}
}
}()
}

View File

@ -1,38 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["flags.go"],
importpath = "k8s.io/kubernetes/pkg/util/flag",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["flags_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/github.com/spf13/pflag:go_default_library"],
)

View File

@ -1,161 +0,0 @@
/*
Copyright 2018 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 flag
import (
"fmt"
"net"
"strconv"
"github.com/spf13/pflag"
"k8s.io/klog"
utilnet "k8s.io/apimachinery/pkg/util/net"
)
// PrintFlags logs the flags in the flagset
func PrintFlags(flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) {
klog.V(1).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
})
}
// TODO(mikedanese): remove these flag wrapper types when we remove command line flags
var (
_ pflag.Value = &IPVar{}
_ pflag.Value = &IPPortVar{}
_ pflag.Value = &PortRangeVar{}
)
// IPVar is used for validating a command line option that represents an IP. It implements the pflag.Value interface
type IPVar struct {
Val *string
}
// Set sets the flag value
func (v IPVar) Set(s string) error {
if len(s) == 0 {
v.Val = nil
return nil
}
if net.ParseIP(s) == nil {
return fmt.Errorf("%q is not a valid IP address", s)
}
if v.Val == nil {
// it's okay to panic here since this is programmer error
panic("the string pointer passed into IPVar should not be nil")
}
*v.Val = s
return nil
}
// String returns the flag value
func (v IPVar) String() string {
if v.Val == nil {
return ""
}
return *v.Val
}
// Type gets the flag type
func (v IPVar) Type() string {
return "ip"
}
// IPPortVar is used for validating a command line option that represents an IP and a port. It implements the pflag.Value interface
type IPPortVar struct {
Val *string
}
// Set sets the flag value
func (v IPPortVar) Set(s string) error {
if len(s) == 0 {
v.Val = nil
return nil
}
if v.Val == nil {
// it's okay to panic here since this is programmer error
panic("the string pointer passed into IPPortVar should not be nil")
}
// Both IP and IP:port are valid.
// Attempt to parse into IP first.
if net.ParseIP(s) != nil {
*v.Val = s
return nil
}
// Can not parse into IP, now assume IP:port.
host, port, err := net.SplitHostPort(s)
if err != nil {
return fmt.Errorf("%q is not in a valid format (ip or ip:port): %v", s, err)
}
if net.ParseIP(host) == nil {
return fmt.Errorf("%q is not a valid IP address", host)
}
if _, err := strconv.Atoi(port); err != nil {
return fmt.Errorf("%q is not a valid number", port)
}
*v.Val = s
return nil
}
// String returns the flag value
func (v IPPortVar) String() string {
if v.Val == nil {
return ""
}
return *v.Val
}
// Type gets the flag type
func (v IPPortVar) Type() string {
return "ipport"
}
// PortRangeVar is used for validating a command line option that represents a port range. It implements the pflag.Value interface
type PortRangeVar struct {
Val *string
}
// Set sets the flag value
func (v PortRangeVar) Set(s string) error {
if _, err := utilnet.ParsePortRange(s); err != nil {
return fmt.Errorf("%q is not a valid port range: %v", s, err)
}
if v.Val == nil {
// it's okay to panic here since this is programmer error
panic("the string pointer passed into PortRangeVar should not be nil")
}
*v.Val = s
return nil
}
// String returns the flag value
func (v PortRangeVar) String() string {
if v.Val == nil {
return ""
}
return *v.Val
}
// Type gets the flag type
func (v PortRangeVar) Type() string {
return "port-range"
}

View File

@ -1,165 +0,0 @@
/*
Copyright 2016 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 flag
import (
"strings"
"testing"
"github.com/spf13/pflag"
)
func TestIPVar(t *testing.T) {
defaultIP := "0.0.0.0"
testCases := []struct {
argc string
expectErr bool
expectVal string
}{
{
argc: "blah --ip=1.2.3.4",
expectVal: "1.2.3.4",
},
{
argc: "blah --ip=1.2.3.4a",
expectErr: true,
expectVal: defaultIP,
},
}
for _, tc := range testCases {
fs := pflag.NewFlagSet("blah", pflag.PanicOnError)
ip := defaultIP
fs.Var(IPVar{&ip}, "ip", "the ip")
var err error
func() {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
fs.Parse(strings.Split(tc.argc, " "))
}()
if tc.expectErr && err == nil {
t.Errorf("did not observe an expected error")
continue
}
if !tc.expectErr && err != nil {
t.Errorf("observed an unexpected error: %v", err)
continue
}
if tc.expectVal != ip {
t.Errorf("unexpected ip: expected %q, saw %q", tc.expectVal, ip)
}
}
}
func TestIPPortVar(t *testing.T) {
defaultIPPort := "0.0.0.0:8080"
testCases := []struct {
desc string
argc string
expectErr bool
expectVal string
}{
{
desc: "valid ipv4 1",
argc: "blah --ipport=0.0.0.0",
expectVal: "0.0.0.0",
},
{
desc: "valid ipv4 2",
argc: "blah --ipport=127.0.0.1",
expectVal: "127.0.0.1",
},
{
desc: "invalid IP",
argc: "blah --ipport=invalidip",
expectErr: true,
expectVal: defaultIPPort,
},
{
desc: "valid ipv4 with port",
argc: "blah --ipport=0.0.0.0:8080",
expectVal: "0.0.0.0:8080",
},
{
desc: "invalid ipv4 with invalid port",
argc: "blah --ipport=0.0.0.0:invalidport",
expectErr: true,
expectVal: defaultIPPort,
},
{
desc: "invalid IP with port",
argc: "blah --ipport=invalidip:8080",
expectErr: true,
expectVal: defaultIPPort,
},
{
desc: "valid ipv6 1",
argc: "blah --ipport=::1",
expectVal: "::1",
},
{
desc: "valid ipv6 2",
argc: "blah --ipport=::",
expectVal: "::",
},
{
desc: "valid ipv6 with port",
argc: "blah --ipport=[::1]:8080",
expectVal: "[::1]:8080",
},
{
desc: "invalid ipv6 with port without bracket",
argc: "blah --ipport=fd00:f00d:600d:f00d:8080",
expectErr: true,
expectVal: defaultIPPort,
},
}
for _, tc := range testCases {
fs := pflag.NewFlagSet("blah", pflag.PanicOnError)
ipport := defaultIPPort
fs.Var(IPPortVar{&ipport}, "ipport", "the ip:port")
var err error
func() {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
fs.Parse(strings.Split(tc.argc, " "))
}()
if tc.expectErr && err == nil {
t.Errorf("%q: Did not observe an expected error", tc.desc)
continue
}
if !tc.expectErr && err != nil {
t.Errorf("%q: Observed an unexpected error: %v", tc.desc, err)
continue
}
if tc.expectVal != ipport {
t.Errorf("%q: Unexpected ipport: expected %q, saw %q", tc.desc, tc.expectVal, ipport)
}
}
}

View File

@ -1,49 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"flock_other.go",
"flock_unix.go",
],
importpath = "k8s.io/kubernetes/pkg/util/flock",
deps = select({
"@io_bazel_rules_go//go/platform:darwin": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,24 +0,0 @@
// +build !linux,!darwin,!freebsd,!openbsd,!netbsd,!dragonfly
/*
Copyright 2016 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 flock
// Acquire is not implemented on non-unix systems.
func Acquire(path string) error {
return nil
}

View File

@ -1,35 +0,0 @@
// +build linux darwin freebsd openbsd netbsd dragonfly
/*
Copyright 2016 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 flock
import "golang.org/x/sys/unix"
// Acquire acquires a lock on a file for the duration of the process. This method
// is reentrant.
func Acquire(path string) error {
fd, err := unix.Open(path, unix.O_CREAT|unix.O_RDWR, 0600)
if err != nil {
return err
}
// We don't need to close the fd since we should hold
// it until the process exits.
return unix.Flock(fd, unix.LOCK_EX)
}

View File

@ -1,41 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["goroutinemap.go"],
importpath = "k8s.io/kubernetes/pkg/util/goroutinemap",
deps = [
"//pkg/util/goroutinemap/exponentialbackoff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["goroutinemap_test.go"],
embed = [":go_default_library"],
deps = ["//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/util/goroutinemap/exponentialbackoff:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -1,4 +0,0 @@
approvers:
- saad-ali
reviewers:
- saad-ali

View File

@ -1,25 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["exponential_backoff.go"],
importpath = "k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,121 +0,0 @@
/*
Copyright 2016 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 exponentialbackoff contains logic for implementing exponential
// backoff for GoRoutineMap and NestedPendingOperations.
package exponentialbackoff
import (
"fmt"
"time"
)
const (
// initialDurationBeforeRetry is the amount of time after an error occurs
// that GoroutineMap will refuse to allow another operation to start with
// the same target (if exponentialBackOffOnError is enabled). Each
// successive error results in a wait 2x times the previous.
initialDurationBeforeRetry time.Duration = 500 * time.Millisecond
// maxDurationBeforeRetry is the maximum amount of time that
// durationBeforeRetry will grow to due to exponential backoff.
// Value is slightly offset from 2 minutes to make timeouts due to this
// constant recognizable.
maxDurationBeforeRetry time.Duration = 2*time.Minute + 2*time.Second
)
// ExponentialBackoff contains the last occurrence of an error and the duration
// that retries are not permitted.
type ExponentialBackoff struct {
lastError error
lastErrorTime time.Time
durationBeforeRetry time.Duration
}
// SafeToRetry returns an error if the durationBeforeRetry period for the given
// lastErrorTime has not yet expired. Otherwise it returns nil.
func (expBackoff *ExponentialBackoff) SafeToRetry(operationName string) error {
if time.Since(expBackoff.lastErrorTime) <= expBackoff.durationBeforeRetry {
return NewExponentialBackoffError(operationName, *expBackoff)
}
return nil
}
func (expBackoff *ExponentialBackoff) Update(err *error) {
if expBackoff.durationBeforeRetry == 0 {
expBackoff.durationBeforeRetry = initialDurationBeforeRetry
} else {
expBackoff.durationBeforeRetry = 2 * expBackoff.durationBeforeRetry
if expBackoff.durationBeforeRetry > maxDurationBeforeRetry {
expBackoff.durationBeforeRetry = maxDurationBeforeRetry
}
}
expBackoff.lastError = *err
expBackoff.lastErrorTime = time.Now()
}
func (expBackoff *ExponentialBackoff) GenerateNoRetriesPermittedMsg(operationName string) string {
return fmt.Sprintf("Operation for %q failed. No retries permitted until %v (durationBeforeRetry %v). Error: %q",
operationName,
expBackoff.lastErrorTime.Add(expBackoff.durationBeforeRetry),
expBackoff.durationBeforeRetry,
expBackoff.lastError)
}
// NewExponentialBackoffError returns a new instance of ExponentialBackoff error.
func NewExponentialBackoffError(
operationName string, expBackoff ExponentialBackoff) error {
return exponentialBackoffError{
operationName: operationName,
expBackoff: expBackoff,
}
}
// IsExponentialBackoff returns true if an error returned from GoroutineMap
// indicates that a new operation can not be started because
// exponentialBackOffOnError is enabled and a previous operation with the same
// operation failed within the durationBeforeRetry period.
func IsExponentialBackoff(err error) bool {
switch err.(type) {
case exponentialBackoffError:
return true
default:
return false
}
}
// exponentialBackoffError is the error returned returned from GoroutineMap when
// a new operation can not be started because exponentialBackOffOnError is
// enabled and a previous operation with the same operation failed within the
// durationBeforeRetry period.
type exponentialBackoffError struct {
operationName string
expBackoff ExponentialBackoff
}
var _ error = exponentialBackoffError{}
func (err exponentialBackoffError) Error() string {
return fmt.Sprintf(
"Failed to create operation with name %q. An operation with that name failed at %v. No retries permitted until %v (%v). Last error: %q.",
err.operationName,
err.expBackoff.lastErrorTime,
err.expBackoff.lastErrorTime.Add(err.expBackoff.durationBeforeRetry),
err.expBackoff.durationBeforeRetry,
err.expBackoff.lastError)
}

View File

@ -1,230 +0,0 @@
/*
Copyright 2016 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 goroutinemap implements a data structure for managing go routines
by name. It prevents the creation of new go routines if an existing go routine
with the same name exists.
*/
package goroutinemap
import (
"fmt"
"sync"
k8sRuntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff"
)
// GoRoutineMap defines a type that can run named goroutines and track their
// state. It prevents the creation of multiple goroutines with the same name
// and may prevent recreation of a goroutine until after the a backoff time
// has elapsed after the last goroutine with that name finished.
type GoRoutineMap interface {
// Run adds operation name to the list of running operations and spawns a
// new go routine to execute the operation.
// If an operation with the same operation name already exists, an
// AlreadyExists or ExponentialBackoff error is returned.
// Once the operation is complete, the go routine is terminated and the
// operation name is removed from the list of executing operations allowing
// a new operation to be started with the same operation name without error.
Run(operationName string, operationFunc func() error) error
// Wait blocks until operations map is empty. This is typically
// necessary during tests - the test should wait until all operations finish
// and evaluate results after that.
Wait()
// WaitForCompletion blocks until either all operations have successfully completed
// or have failed but are not pending. The test should wait until operations are either
// complete or have failed.
WaitForCompletion()
// IsOperationPending returns true if the operation is pending (currently
// running), otherwise returns false.
IsOperationPending(operationName string) bool
}
// NewGoRoutineMap returns a new instance of GoRoutineMap.
func NewGoRoutineMap(exponentialBackOffOnError bool) GoRoutineMap {
g := &goRoutineMap{
operations: make(map[string]operation),
exponentialBackOffOnError: exponentialBackOffOnError,
}
g.cond = sync.NewCond(&g.lock)
return g
}
type goRoutineMap struct {
operations map[string]operation
exponentialBackOffOnError bool
cond *sync.Cond
lock sync.RWMutex
}
// operation holds the state of a single goroutine.
type operation struct {
operationPending bool
expBackoff exponentialbackoff.ExponentialBackoff
}
func (grm *goRoutineMap) Run(
operationName string,
operationFunc func() error) error {
grm.lock.Lock()
defer grm.lock.Unlock()
existingOp, exists := grm.operations[operationName]
if exists {
// Operation with name exists
if existingOp.operationPending {
return NewAlreadyExistsError(operationName)
}
if err := existingOp.expBackoff.SafeToRetry(operationName); err != nil {
return err
}
}
grm.operations[operationName] = operation{
operationPending: true,
expBackoff: existingOp.expBackoff,
}
go func() (err error) {
// Handle unhandled panics (very unlikely)
defer k8sRuntime.HandleCrash()
// Handle completion of and error, if any, from operationFunc()
defer grm.operationComplete(operationName, &err)
// Handle panic, if any, from operationFunc()
defer k8sRuntime.RecoverFromPanic(&err)
return operationFunc()
}()
return nil
}
// operationComplete handles the completion of a goroutine run in the
// goRoutineMap.
func (grm *goRoutineMap) operationComplete(
operationName string, err *error) {
// Defer operations are executed in Last-In is First-Out order. In this case
// the lock is acquired first when operationCompletes begins, and is
// released when the method finishes, after the lock is released cond is
// signaled to wake waiting goroutine.
defer grm.cond.Signal()
grm.lock.Lock()
defer grm.lock.Unlock()
if *err == nil || !grm.exponentialBackOffOnError {
// Operation completed without error, or exponentialBackOffOnError disabled
delete(grm.operations, operationName)
if *err != nil {
// Log error
klog.Errorf("operation for %q failed with: %v",
operationName,
*err)
}
} else {
// Operation completed with error and exponentialBackOffOnError Enabled
existingOp := grm.operations[operationName]
existingOp.expBackoff.Update(err)
existingOp.operationPending = false
grm.operations[operationName] = existingOp
// Log error
klog.Errorf("%v",
existingOp.expBackoff.GenerateNoRetriesPermittedMsg(operationName))
}
}
func (grm *goRoutineMap) IsOperationPending(operationName string) bool {
grm.lock.RLock()
defer grm.lock.RUnlock()
existingOp, exists := grm.operations[operationName]
if exists && existingOp.operationPending {
return true
}
return false
}
func (grm *goRoutineMap) Wait() {
grm.lock.Lock()
defer grm.lock.Unlock()
for len(grm.operations) > 0 {
grm.cond.Wait()
}
}
func (grm *goRoutineMap) WaitForCompletion() {
grm.lock.Lock()
defer grm.lock.Unlock()
for {
if len(grm.operations) == 0 || grm.nothingPending() {
break
} else {
grm.cond.Wait()
}
}
}
// Check if any operation is pending. Already assumes caller has the
// necessary locks
func (grm *goRoutineMap) nothingPending() bool {
nothingIsPending := true
for _, operation := range grm.operations {
if operation.operationPending {
nothingIsPending = false
break
}
}
return nothingIsPending
}
// NewAlreadyExistsError returns a new instance of AlreadyExists error.
func NewAlreadyExistsError(operationName string) error {
return alreadyExistsError{operationName}
}
// IsAlreadyExists returns true if an error returned from GoRoutineMap indicates
// a new operation can not be started because an operation with the same
// operation name is already executing.
func IsAlreadyExists(err error) bool {
switch err.(type) {
case alreadyExistsError:
return true
default:
return false
}
}
// alreadyExistsError is the error returned by GoRoutineMap when a new operation
// can not be started because an operation with the same operation name is
// already executing.
type alreadyExistsError struct {
operationName string
}
var _ error = alreadyExistsError{}
func (err alreadyExistsError) Error() string {
return fmt.Sprintf(
"Failed to create operation with name %q. An operation with that name is already executing.",
err.operationName)
}

View File

@ -1,531 +0,0 @@
/*
Copyright 2016 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 goroutinemap
import (
"fmt"
"testing"
"time"
"k8s.io/apimachinery/pkg/util/wait"
)
const (
// testTimeout is a timeout of goroutines to finish. This _should_ be just a
// "context switch" and it should take several ms, however, Clayton says "We
// have had flakes due to tests that assumed that 15s is long enough to sleep")
testTimeout time.Duration = 1 * time.Minute
// initialOperationWaitTimeShort is the initial amount of time the test will
// wait for an operation to complete (each successive failure results in
// exponential backoff).
initialOperationWaitTimeShort time.Duration = 20 * time.Millisecond
// initialOperationWaitTimeLong is the initial amount of time the test will
// wait for an operation to complete (each successive failure results in
// exponential backoff).
initialOperationWaitTimeLong time.Duration = 500 * time.Millisecond
)
func Test_NewGoRoutineMap_Positive_SingleOp(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
operationName := "operation-name"
operation := func() error { return nil }
// Act
err := grm.Run(operationName, operation)
// Assert
if err != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
}
}
func Test_NewGoRoutineMap_Positive_TwoOps(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
operation1Name := "operation1-name"
operation2Name := "operation2-name"
operation := func() error { return nil }
// Act
err1 := grm.Run(operation1Name, operation)
err2 := grm.Run(operation2Name, operation)
// Assert
if err1 != nil {
t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", operation1Name, err1)
}
if err2 != nil {
t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", operation2Name, err2)
}
}
func Test_NewGoRoutineMap_Positive_SingleOpWithExpBackoff(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
operationName := "operation-name"
operation := func() error { return nil }
// Act
err := grm.Run(operationName, operation)
// Assert
if err != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletes(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateCallbackFunc(operation1DoneCh)
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
<-operation1DoneCh // Force operation1 to complete
// Act
err2 := retryWithExponentialBackOff(
time.Duration(initialOperationWaitTimeShort),
func() (bool, error) {
err := grm.Run(operationName, operation2)
if err != nil {
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
return false, nil
}
return true, nil
},
)
// Assert
if err2 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletesWithExpBackoff(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateCallbackFunc(operation1DoneCh)
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
<-operation1DoneCh // Force operation1 to complete
// Act
err2 := retryWithExponentialBackOff(
time.Duration(initialOperationWaitTimeShort),
func() (bool, error) {
err := grm.Run(operationName, operation2)
if err != nil {
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
return false, nil
}
return true, nil
},
)
// Assert
if err2 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanics(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1 := generatePanicFunc()
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
// Act
err2 := retryWithExponentialBackOff(
time.Duration(initialOperationWaitTimeShort),
func() (bool, error) {
err := grm.Run(operationName, operation2)
if err != nil {
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
return false, nil
}
return true, nil
},
)
// Assert
if err2 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanicsWithExpBackoff(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1 := generatePanicFunc()
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
// Act
err2 := retryWithExponentialBackOff(
time.Duration(initialOperationWaitTimeLong), // Longer duration to accommodate for backoff
func() (bool, error) {
err := grm.Run(operationName, operation2)
if err != nil {
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
return false, nil
}
return true, nil
},
)
// Assert
if err2 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
}
}
func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletes(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateWaitFunc(operation1DoneCh)
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
// Act
err2 := grm.Run(operationName, operation2)
// Assert
if err2 == nil {
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", operationName)
}
if !IsAlreadyExists(err2) {
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
}
}
func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletesWithExpBackoff(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateWaitFunc(operation1DoneCh)
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
// Act
err2 := grm.Run(operationName, operation2)
// Assert
if err2 == nil {
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", operationName)
}
if !IsAlreadyExists(err2) {
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
}
}
func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletes(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateWaitFunc(operation1DoneCh)
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
operation3 := generateNoopFunc()
// Act
err2 := grm.Run(operationName, operation2)
// Assert
if err2 == nil {
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", operationName)
}
if !IsAlreadyExists(err2) {
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
}
// Act
operation1DoneCh <- true // Force operation1 to complete
err3 := retryWithExponentialBackOff(
time.Duration(initialOperationWaitTimeShort),
func() (bool, error) {
err := grm.Run(operationName, operation3)
if err != nil {
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
return false, nil
}
return true, nil
},
)
// Assert
if err3 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err3)
}
}
func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletesWithExpBackoff(t *testing.T) {
// Arrange
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateWaitFunc(operation1DoneCh)
err1 := grm.Run(operationName, operation1)
if err1 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
}
operation2 := generateNoopFunc()
operation3 := generateNoopFunc()
// Act
err2 := grm.Run(operationName, operation2)
// Assert
if err2 == nil {
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", operationName)
}
if !IsAlreadyExists(err2) {
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
}
// Act
operation1DoneCh <- true // Force operation1 to complete
err3 := retryWithExponentialBackOff(
time.Duration(initialOperationWaitTimeShort),
func() (bool, error) {
err := grm.Run(operationName, operation3)
if err != nil {
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
return false, nil
}
return true, nil
},
)
// Assert
if err3 != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err3)
}
}
func Test_NewGoRoutineMap_Positive_WaitEmpty(t *testing.T) {
// Test than Wait() on empty GoRoutineMap always succeeds without blocking
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
// Act
waitDoneCh := make(chan interface{}, 1)
go func() {
grm.Wait()
waitDoneCh <- true
}()
// Assert
err := waitChannelWithTimeout(waitDoneCh, testTimeout)
if err != nil {
t.Errorf("Error waiting for GoRoutineMap.Wait: %v", err)
}
}
func Test_NewGoRoutineMap_Positive_WaitEmptyWithExpBackoff(t *testing.T) {
// Test than Wait() on empty GoRoutineMap always succeeds without blocking
// Arrange
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
// Act
waitDoneCh := make(chan interface{}, 1)
go func() {
grm.Wait()
waitDoneCh <- true
}()
// Assert
err := waitChannelWithTimeout(waitDoneCh, testTimeout)
if err != nil {
t.Errorf("Error waiting for GoRoutineMap.Wait: %v", err)
}
}
func Test_NewGoRoutineMap_Positive_Wait(t *testing.T) {
// Test that Wait() really blocks until the last operation succeeds
// Arrange
grm := NewGoRoutineMap(false /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateWaitFunc(operation1DoneCh)
err := grm.Run(operationName, operation1)
if err != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
}
// Act
waitDoneCh := make(chan interface{}, 1)
go func() {
grm.Wait()
waitDoneCh <- true
}()
// Finish the operation
operation1DoneCh <- true
// Assert
err = waitChannelWithTimeout(waitDoneCh, testTimeout)
if err != nil {
t.Fatalf("Error waiting for GoRoutineMap.Wait: %v", err)
}
}
func Test_NewGoRoutineMap_Positive_WaitWithExpBackoff(t *testing.T) {
// Test that Wait() really blocks until the last operation succeeds
// Arrange
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
operationName := "operation-name"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateWaitFunc(operation1DoneCh)
err := grm.Run(operationName, operation1)
if err != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
}
// Act
waitDoneCh := make(chan interface{}, 1)
go func() {
grm.Wait()
waitDoneCh <- true
}()
// Finish the operation
operation1DoneCh <- true
// Assert
err = waitChannelWithTimeout(waitDoneCh, testTimeout)
if err != nil {
t.Fatalf("Error waiting for GoRoutineMap.Wait: %v", err)
}
}
func Test_NewGoRoutineMap_WaitForCompletionWithExpBackoff(t *testing.T) {
grm := NewGoRoutineMap(true /* exponentialBackOffOnError */)
operationName := "operation-err"
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
operation1 := generateErrorFunc(operation1DoneCh)
err := grm.Run(operationName, operation1)
if err != nil {
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
}
// Act
waitDoneCh := make(chan interface{}, 1)
go func() {
grm.WaitForCompletion()
waitDoneCh <- true
}()
// Finish the operation
operation1DoneCh <- true
// Assert that WaitForCompletion returns even if scheduled op had error
err = waitChannelWithTimeout(waitDoneCh, testTimeout)
if err != nil {
t.Fatalf("Error waiting for GoRoutineMap.Wait: %v", err)
}
}
func generateCallbackFunc(done chan<- interface{}) func() error {
return func() error {
done <- true
return nil
}
}
func generateErrorFunc(done <-chan interface{}) func() error {
return func() error {
<-done
return fmt.Errorf("Generic error")
}
}
func generateWaitFunc(done <-chan interface{}) func() error {
return func() error {
<-done
return nil
}
}
func generatePanicFunc() func() error {
return func() error {
panic("testing panic")
}
}
func generateNoopFunc() func() error {
return func() error { return nil }
}
func retryWithExponentialBackOff(initialDuration time.Duration, fn wait.ConditionFunc) error {
backoff := wait.Backoff{
Duration: initialDuration,
Factor: 3,
Jitter: 0,
Steps: 4,
}
return wait.ExponentialBackoff(backoff, fn)
}
func waitChannelWithTimeout(ch <-chan interface{}, timeout time.Duration) error {
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case <-ch:
// Success!
return nil
case <-timer.C:
return fmt.Errorf("timeout after %v", timeout)
}
}

View File

@ -1,34 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["hash.go"],
importpath = "k8s.io/kubernetes/pkg/util/hash",
deps = ["//vendor/github.com/davecgh/go-spew/spew:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["hash_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/github.com/davecgh/go-spew/spew:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,37 +0,0 @@
/*
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 hash
import (
"hash"
"github.com/davecgh/go-spew/spew"
)
// DeepHashObject writes specified object to hash using the spew library
// which follows pointers and prints actual values of the nested objects
// ensuring the hash does not change when a pointer changes.
func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) {
hasher.Reset()
printer := spew.ConfigState{
Indent: " ",
SortKeys: true,
DisableMethods: true,
SpewKeys: true,
}
printer.Fprintf(hasher, "%#v", objectToWrite)
}

View File

@ -1,147 +0,0 @@
/*
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 hash
import (
"fmt"
"hash/adler32"
"testing"
"github.com/davecgh/go-spew/spew"
)
type A struct {
x int
y string
}
type B struct {
x []int
y map[string]bool
}
type C struct {
x int
y string
}
func (c C) String() string {
return fmt.Sprintf("%d:%s", c.x, c.y)
}
func TestDeepHashObject(t *testing.T) {
successCases := []func() interface{}{
func() interface{} { return 8675309 },
func() interface{} { return "Jenny, I got your number" },
func() interface{} { return []string{"eight", "six", "seven"} },
func() interface{} { return [...]int{5, 3, 0, 9} },
func() interface{} { return map[int]string{8: "8", 6: "6", 7: "7"} },
func() interface{} { return map[string]int{"5": 5, "3": 3, "0": 0, "9": 9} },
func() interface{} { return A{867, "5309"} },
func() interface{} { return &A{867, "5309"} },
func() interface{} {
return B{[]int{8, 6, 7}, map[string]bool{"5": true, "3": true, "0": true, "9": true}}
},
func() interface{} { return map[A]bool{{8675309, "Jenny"}: true, {9765683, "!Jenny"}: false} },
func() interface{} { return map[C]bool{{8675309, "Jenny"}: true, {9765683, "!Jenny"}: false} },
func() interface{} { return map[*A]bool{{8675309, "Jenny"}: true, {9765683, "!Jenny"}: false} },
func() interface{} { return map[*C]bool{{8675309, "Jenny"}: true, {9765683, "!Jenny"}: false} },
}
for _, tc := range successCases {
hasher1 := adler32.New()
DeepHashObject(hasher1, tc())
hash1 := hasher1.Sum32()
DeepHashObject(hasher1, tc())
hash2 := hasher1.Sum32()
if hash1 != hash2 {
t.Fatalf("hash of the same object (%q) produced different results: %d vs %d", toString(tc()), hash1, hash2)
}
for i := 0; i < 100; i++ {
hasher2 := adler32.New()
DeepHashObject(hasher1, tc())
hash1a := hasher1.Sum32()
DeepHashObject(hasher2, tc())
hash2a := hasher2.Sum32()
if hash1a != hash1 {
t.Errorf("repeated hash of the same object (%q) produced different results: %d vs %d", toString(tc()), hash1, hash1a)
}
if hash2a != hash2 {
t.Errorf("repeated hash of the same object (%q) produced different results: %d vs %d", toString(tc()), hash2, hash2a)
}
if hash1a != hash2a {
t.Errorf("hash of the same object produced (%q) different results: %d vs %d", toString(tc()), hash1a, hash2a)
}
}
}
}
func toString(obj interface{}) string {
return spew.Sprintf("%#v", obj)
}
type wheel struct {
radius uint32
}
type unicycle struct {
primaryWheel *wheel
licencePlateID string
tags map[string]string
}
func TestDeepObjectPointer(t *testing.T) {
// Arrange
wheel1 := wheel{radius: 17}
wheel2 := wheel{radius: 22}
wheel3 := wheel{radius: 17}
myUni1 := unicycle{licencePlateID: "blah", primaryWheel: &wheel1, tags: map[string]string{"color": "blue", "name": "john"}}
myUni2 := unicycle{licencePlateID: "blah", primaryWheel: &wheel2, tags: map[string]string{"color": "blue", "name": "john"}}
myUni3 := unicycle{licencePlateID: "blah", primaryWheel: &wheel3, tags: map[string]string{"color": "blue", "name": "john"}}
// Run it more than once to verify determinism of hasher.
for i := 0; i < 100; i++ {
hasher1 := adler32.New()
hasher2 := adler32.New()
hasher3 := adler32.New()
// Act
DeepHashObject(hasher1, myUni1)
hash1 := hasher1.Sum32()
DeepHashObject(hasher1, myUni1)
hash1a := hasher1.Sum32()
DeepHashObject(hasher2, myUni2)
hash2 := hasher2.Sum32()
DeepHashObject(hasher3, myUni3)
hash3 := hasher3.Sum32()
// Assert
if hash1 != hash1a {
t.Errorf("repeated hash of the same object produced different results: %d vs %d", hash1, hash1a)
}
if hash1 == hash2 {
t.Errorf("hash1 (%d) and hash2(%d) must be different because they have different values for wheel size", hash1, hash2)
}
if hash1 != hash3 {
t.Errorf("hash1 (%d) and hash3(%d) must be the same because although they point to different objects, they have the same values for wheel size", hash1, hash3)
}
}
}

View File

@ -1,25 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["initsystem.go"],
importpath = "k8s.io/kubernetes/pkg/util/initsystem",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,179 +0,0 @@
/*
Copyright 2016 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 initsystem
import (
"fmt"
"os/exec"
"strings"
)
type InitSystem interface {
// ServiceStart tries to start a specific service
ServiceStart(service string) error
// ServiceStop tries to stop a specific service
ServiceStop(service string) error
// ServiceRestart tries to reload the environment and restart the specific service
ServiceRestart(service string) error
// ServiceExists ensures the service is defined for this init system.
ServiceExists(service string) bool
// ServiceIsEnabled ensures the service is enabled to start on each boot.
ServiceIsEnabled(service string) bool
// ServiceIsActive ensures the service is running, or attempting to run. (crash looping in the case of kubelet)
ServiceIsActive(service string) bool
}
type SystemdInitSystem struct{}
func (sysd SystemdInitSystem) reloadSystemd() error {
if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil {
return fmt.Errorf("failed to reload systemd: %v", err)
}
return nil
}
func (sysd SystemdInitSystem) ServiceStart(service string) error {
// Before we try to start any service, make sure that systemd is ready
if err := sysd.reloadSystemd(); err != nil {
return err
}
args := []string{"start", service}
return exec.Command("systemctl", args...).Run()
}
func (sysd SystemdInitSystem) ServiceRestart(service string) error {
// Before we try to restart any service, make sure that systemd is ready
if err := sysd.reloadSystemd(); err != nil {
return err
}
args := []string{"restart", service}
return exec.Command("systemctl", args...).Run()
}
func (sysd SystemdInitSystem) ServiceStop(service string) error {
args := []string{"stop", service}
return exec.Command("systemctl", args...).Run()
}
func (sysd SystemdInitSystem) ServiceExists(service string) bool {
args := []string{"status", service}
outBytes, _ := exec.Command("systemctl", args...).Output()
output := string(outBytes)
if strings.Contains(output, "Loaded: not-found") {
return false
}
return true
}
func (sysd SystemdInitSystem) ServiceIsEnabled(service string) bool {
args := []string{"is-enabled", service}
err := exec.Command("systemctl", args...).Run()
if err != nil {
return false
}
return true
}
// ServiceIsActive will check is the service is "active". In the case of
// crash looping services (kubelet in our case) status will return as
// "activating", so we will consider this active as well.
func (sysd SystemdInitSystem) ServiceIsActive(service string) bool {
args := []string{"is-active", service}
// Ignoring error here, command returns non-0 if in "activating" status:
outBytes, _ := exec.Command("systemctl", args...).Output()
output := strings.TrimSpace(string(outBytes))
if output == "active" || output == "activating" {
return true
}
return false
}
// WindowsInitSystem is the windows implementation of InitSystem
type WindowsInitSystem struct{}
func (sysd WindowsInitSystem) ServiceStart(service string) error {
args := []string{"Start-Service", service}
err := exec.Command("powershell", args...).Run()
return err
}
func (sysd WindowsInitSystem) ServiceRestart(service string) error {
if err := sysd.ServiceStop(service); err != nil {
return fmt.Errorf("couldn't stop service: %v", err)
}
if err := sysd.ServiceStart(service); err != nil {
return fmt.Errorf("couldn't start service: %v", err)
}
return nil
}
func (sysd WindowsInitSystem) ServiceStop(service string) error {
args := []string{"Stop-Service", service}
err := exec.Command("powershell", args...).Run()
return err
}
func (sysd WindowsInitSystem) ServiceExists(service string) bool {
args := []string{"Get-Service", service}
err := exec.Command("powershell", args...).Run()
if err != nil {
return false
}
return true
}
func (sysd WindowsInitSystem) ServiceIsEnabled(service string) bool {
args := []string{"Get-Service", service + "| select -property starttype"}
outBytes, _ := exec.Command("powershell", args...).Output()
output := strings.TrimSpace(string(outBytes))
if strings.Contains(output, "Automatic") {
return true
}
return false
}
func (sysd WindowsInitSystem) ServiceIsActive(service string) bool {
args := []string{"Get-Service", service + "| select -property status"}
outBytes, _ := exec.Command("powershell", args...).Output()
output := strings.TrimSpace(string(outBytes))
if strings.Contains(output, "Running") {
return true
}
return false
}
// GetInitSystem returns an InitSystem for the current system, or nil
// if we cannot detect a supported init system for pre-flight checks.
// This indicates we will skip init system checks, not an error.
func GetInitSystem() (InitSystem, error) {
// Assume existence of systemctl in path implies this is a systemd system:
_, err := exec.LookPath("systemctl")
if err == nil {
return &SystemdInitSystem{}, nil
}
_, err = exec.LookPath("wininit.exe")
if err == nil {
return &WindowsInitSystem{}, nil
}
return nil, fmt.Errorf("no supported init system detected, skipping checking for services")
}

View File

@ -1,25 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["interrupt.go"],
importpath = "k8s.io/kubernetes/pkg/util/interrupt",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,104 +0,0 @@
/*
Copyright 2016 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 interrupt
import (
"os"
"os/signal"
"sync"
"syscall"
)
// terminationSignals are signals that cause the program to exit in the
// supported platforms (linux, darwin, windows).
var terminationSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT}
// Handler guarantees execution of notifications after a critical section (the function passed
// to a Run method), even in the presence of process termination. It guarantees exactly once
// invocation of the provided notify functions.
type Handler struct {
notify []func()
final func(os.Signal)
once sync.Once
}
// Chain creates a new handler that invokes all notify functions when the critical section exits
// and then invokes the optional handler's notifications. This allows critical sections to be
// nested without losing exactly once invocations. Notify functions can invoke any cleanup needed
// but should not exit (which is the responsibility of the parent handler).
func Chain(handler *Handler, notify ...func()) *Handler {
if handler == nil {
return New(nil, notify...)
}
return New(handler.Signal, append(notify, handler.Close)...)
}
// New creates a new handler that guarantees all notify functions are run after the critical
// section exits (or is interrupted by the OS), then invokes the final handler. If no final
// handler is specified, the default final is `os.Exit(1)`. A handler can only be used for
// one critical section.
func New(final func(os.Signal), notify ...func()) *Handler {
return &Handler{
final: final,
notify: notify,
}
}
// Close executes all the notification handlers if they have not yet been executed.
func (h *Handler) Close() {
h.once.Do(func() {
for _, fn := range h.notify {
fn()
}
})
}
// Signal is called when an os.Signal is received, and guarantees that all notifications
// are executed, then the final handler is executed. This function should only be called once
// per Handler instance.
func (h *Handler) Signal(s os.Signal) {
h.once.Do(func() {
for _, fn := range h.notify {
fn()
}
if h.final == nil {
os.Exit(1)
}
h.final(s)
})
}
// Run ensures that any notifications are invoked after the provided fn exits (even if the
// process is interrupted by an OS termination signal). Notifications are only invoked once
// per Handler instance, so calling Run more than once will not behave as the user expects.
func (h *Handler) Run(fn func() error) error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, terminationSignals...)
defer func() {
signal.Stop(ch)
close(ch)
}()
go func() {
sig, ok := <-ch
if !ok {
return
}
h.Signal(sig)
}()
defer h.Close()
return fn()
}

View File

@ -1,25 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["consistentread.go"],
importpath = "k8s.io/kubernetes/pkg/util/io",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,40 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"ipconfig.go",
],
importpath = "k8s.io/kubernetes/pkg/util/ipconfig",
deps = [
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["ipconfig_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/k8s.io/utils/exec:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,18 +0,0 @@
/*
Copyright 2016 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 ipconfig provides an interface and implementations for running Windows ipconfig commands.
package ipconfig // import "k8s.io/kubernetes/pkg/util/ipconfig"

View File

@ -1,99 +0,0 @@
/*
Copyright 2016 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 ipconfig
import (
"runtime"
"strings"
"sync"
"k8s.io/klog"
utilexec "k8s.io/utils/exec"
)
// Interface is an injectable interface for running ipconfig commands. Implementations must be goroutine-safe.
type Interface interface {
// GetDNSSuffixSearchList returns the list of DNS suffix to search
GetDNSSuffixSearchList() ([]string, error)
}
const (
cmdIpconfig string = "ipconfig"
cmdDefaultArgs string = "/all"
dnsSuffixSearchLisLabel string = "DNS Suffix Search List"
)
// runner implements Interface in terms of exec("ipconfig").
type runner struct {
mu sync.Mutex
exec utilexec.Interface
}
// New returns a new Interface which will exec ipconfig.
func New(exec utilexec.Interface) Interface {
runner := &runner{
exec: exec,
}
return runner
}
// GetDNSSuffixSearchList returns the list of DNS suffix to search
func (runner *runner) GetDNSSuffixSearchList() ([]string, error) {
// Parse the DNS suffix search list from ipconfig output
// ipconfig /all on Windows displays the entry of DNS suffix search list
// An example output contains:
//
// DNS Suffix Search List. . . . . . : example1.com
// example2.com
//
// TODO: this does not work when the label is localized
suffixList := []string{}
if runtime.GOOS != "windows" {
klog.V(1).Infof("ipconfig not supported on GOOS=%s", runtime.GOOS)
return suffixList, nil
}
out, err := runner.exec.Command(cmdIpconfig, cmdDefaultArgs).Output()
if err == nil {
lines := strings.Split(string(out), "\n")
for i, line := range lines {
if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, dnsSuffixSearchLisLabel) {
if parts := strings.Split(trimmed, ":"); len(parts) > 1 {
if trimmed := strings.TrimSpace(parts[1]); trimmed != "" {
suffixList = append(suffixList, strings.TrimSpace(parts[1]))
}
for j := i + 1; j < len(lines); j++ {
if trimmed := strings.TrimSpace(lines[j]); trimmed != "" && !strings.Contains(trimmed, ":") {
suffixList = append(suffixList, trimmed)
} else {
break
}
}
}
break
}
}
} else {
klog.V(1).Infof("Running %s %s failed: %v", cmdIpconfig, cmdDefaultArgs, err)
}
return suffixList, err
}

View File

@ -1,33 +0,0 @@
/*
Copyright 2016 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 ipconfig
import (
"testing"
"k8s.io/utils/exec"
)
func TestGetDNSSuffixSearchList(t *testing.T) {
// Simple test
ipconfigInterface := New(exec.New())
_, err := ipconfigInterface.GetDNSSuffixSearchList()
if err != nil {
t.Errorf("expected success, got %v", err)
}
}

View File

@ -1,43 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"ipset.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/util/ipset",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["ipset_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/util/ipset/testing:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,9 +0,0 @@
reviewers:
- thockin
- brendandburns
- m1093782566
- islinwb
approvers:
- thockin
- brendandburns
- m1093782566

View File

@ -1,528 +0,0 @@
/*
Copyright 2017 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 ipset
import (
"bytes"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"k8s.io/klog"
utilexec "k8s.io/utils/exec"
)
// Interface is an injectable interface for running ipset commands. Implementations must be goroutine-safe.
type Interface interface {
// FlushSet deletes all entries from a named set.
FlushSet(set string) error
// DestroySet deletes a named set.
DestroySet(set string) error
// DestroyAllSets deletes all sets.
DestroyAllSets() error
// CreateSet creates a new set. It will ignore error when the set already exists if ignoreExistErr=true.
CreateSet(set *IPSet, ignoreExistErr bool) error
// AddEntry adds a new entry to the named set. It will ignore error when the entry already exists if ignoreExistErr=true.
AddEntry(entry string, set *IPSet, ignoreExistErr bool) error
// DelEntry deletes one entry from the named set
DelEntry(entry string, set string) error
// Test test if an entry exists in the named set
TestEntry(entry string, set string) (bool, error)
// ListEntries lists all the entries from a named set
ListEntries(set string) ([]string, error)
// ListSets list all set names from kernel
ListSets() ([]string, error)
// GetVersion returns the "X.Y" version string for ipset.
GetVersion() (string, error)
}
// IPSetCmd represents the ipset util. We use ipset command for ipset execute.
const IPSetCmd = "ipset"
// EntryMemberPattern is the regular expression pattern of ipset member list.
// The raw output of ipset command `ipset list {set}` is similar to,
//Name: foobar
//Type: hash:ip,port
//Revision: 2
//Header: family inet hashsize 1024 maxelem 65536
//Size in memory: 16592
//References: 0
//Members:
//192.168.1.2,tcp:8080
//192.168.1.1,udp:53
var EntryMemberPattern = "(?m)^(.*\n)*Members:\n"
// VersionPattern is the regular expression pattern of ipset version string.
// ipset version output is similar to "v6.10".
var VersionPattern = "v[0-9]+\\.[0-9]+"
// IPSet implements an Interface to a set.
type IPSet struct {
// Name is the set name.
Name string
// SetType specifies the ipset type.
SetType Type
// HashFamily specifies the protocol family of the IP addresses to be stored in the set.
// The default is inet, i.e IPv4. If users want to use IPv6, they should specify inet6.
HashFamily string
// HashSize specifies the hash table size of ipset.
HashSize int
// MaxElem specifies the max element number of ipset.
MaxElem int
// PortRange specifies the port range of bitmap:port type ipset.
PortRange string
// comment message for ipset
Comment string
}
// Validate checks if a given ipset is valid or not.
func (set *IPSet) Validate() bool {
// Check if protocol is valid for `HashIPPort`, `HashIPPortIP` and `HashIPPortNet` type set.
if set.SetType == HashIPPort || set.SetType == HashIPPortIP || set.SetType == HashIPPortNet {
if valid := validateHashFamily(set.HashFamily); !valid {
return false
}
}
// check set type
if valid := validateIPSetType(set.SetType); !valid {
return false
}
// check port range for bitmap type set
if set.SetType == BitmapPort {
if valid := validatePortRange(set.PortRange); !valid {
return false
}
}
// check hash size value of ipset
if set.HashSize <= 0 {
klog.Errorf("Invalid hashsize value %d, should be >0", set.HashSize)
return false
}
// check max elem value of ipset
if set.MaxElem <= 0 {
klog.Errorf("Invalid maxelem value %d, should be >0", set.MaxElem)
return false
}
return true
}
//setIPSetDefaults sets some IPSet fields if not present to their default values.
func (set *IPSet) setIPSetDefaults() {
// Setting default values if not present
if set.HashSize == 0 {
set.HashSize = 1024
}
if set.MaxElem == 0 {
set.MaxElem = 65536
}
// Default protocol is IPv4
if set.HashFamily == "" {
set.HashFamily = ProtocolFamilyIPV4
}
// Default ipset type is "hash:ip,port"
if len(set.SetType) == 0 {
set.SetType = HashIPPort
}
if len(set.PortRange) == 0 {
set.PortRange = DefaultPortRange
}
}
// Entry represents a ipset entry.
type Entry struct {
// IP is the entry's IP. The IP address protocol corresponds to the HashFamily of IPSet.
// All entries' IP addresses in the same ip set has same the protocol, IPv4 or IPv6.
IP string
// Port is the entry's Port.
Port int
// Protocol is the entry's Protocol. The protocols of entries in the same ip set are all
// the same. The accepted protocols are TCP, UDP and SCTP.
Protocol string
// Net is the entry's IP network address. Network address with zero prefix size can NOT
// be stored.
Net string
// IP2 is the entry's second IP. IP2 may not be empty for `hash:ip,port,ip` type ip set.
IP2 string
// SetType is the type of ipset where the entry exists.
SetType Type
}
// Validate checks if a given ipset entry is valid or not. The set parameter is the ipset that entry belongs to.
func (e *Entry) Validate(set *IPSet) bool {
if e.Port < 0 {
klog.Errorf("Entry %v port number %d should be >=0 for ipset %v", e, e.Port, set)
return false
}
switch e.SetType {
case HashIPPort:
//check if IP and Protocol of Entry is valid.
if valid := e.checkIPandProtocol(set); !valid {
return false
}
case HashIPPortIP:
//check if IP and Protocol of Entry is valid.
if valid := e.checkIPandProtocol(set); !valid {
return false
}
// IP2 can not be empty for `hash:ip,port,ip` type ip set
if net.ParseIP(e.IP2) == nil {
klog.Errorf("Error parsing entry %v second ip address %v for ipset %v", e, e.IP2, set)
return false
}
case HashIPPortNet:
//check if IP and Protocol of Entry is valid.
if valid := e.checkIPandProtocol(set); !valid {
return false
}
// Net can not be empty for `hash:ip,port,net` type ip set
if _, ipNet, err := net.ParseCIDR(e.Net); ipNet == nil {
klog.Errorf("Error parsing entry %v ip net %v for ipset %v, error: %v", e, e.Net, set, err)
return false
}
case BitmapPort:
// check if port number satisfies its ipset's requirement of port range
if set == nil {
klog.Errorf("Unable to reference ip set where the entry %v exists", e)
return false
}
begin, end, err := parsePortRange(set.PortRange)
if err != nil {
klog.Errorf("Failed to parse set %v port range %s for ipset %v, error: %v", set, set.PortRange, set, err)
return false
}
if e.Port < begin || e.Port > end {
klog.Errorf("Entry %v port number %d is not in the port range %s of its ipset %v", e, e.Port, set.PortRange, set)
return false
}
}
return true
}
// String returns the string format for ipset entry.
func (e *Entry) String() string {
switch e.SetType {
case HashIPPort:
// Entry{192.168.1.1, udp, 53} -> 192.168.1.1,udp:53
// Entry{192.168.1.2, tcp, 8080} -> 192.168.1.2,tcp:8080
return fmt.Sprintf("%s,%s:%s", e.IP, e.Protocol, strconv.Itoa(e.Port))
case HashIPPortIP:
// Entry{192.168.1.1, udp, 53, 10.0.0.1} -> 192.168.1.1,udp:53,10.0.0.1
// Entry{192.168.1.2, tcp, 8080, 192.168.1.2} -> 192.168.1.2,tcp:8080,192.168.1.2
return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.IP2)
case HashIPPortNet:
// Entry{192.168.1.2, udp, 80, 10.0.1.0/24} -> 192.168.1.2,udp:80,10.0.1.0/24
// Entry{192.168.2,25, tcp, 8080, 10.1.0.0/16} -> 192.168.2,25,tcp:8080,10.1.0.0/16
return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.Net)
case BitmapPort:
// Entry{53} -> 53
// Entry{8080} -> 8080
return strconv.Itoa(e.Port)
}
return ""
}
// checkIPandProtocol checks if IP and Protocol of Entry is valid.
func (e *Entry) checkIPandProtocol(set *IPSet) bool {
// set default protocol to tcp if empty
if len(e.Protocol) == 0 {
e.Protocol = ProtocolTCP
} else if !validateProtocol(e.Protocol) {
return false
}
if net.ParseIP(e.IP) == nil {
klog.Errorf("Error parsing entry %v ip address %v for ipset %v", e, e.IP, set)
return false
}
return true
}
type runner struct {
exec utilexec.Interface
}
// New returns a new Interface which will exec ipset.
func New(exec utilexec.Interface) Interface {
return &runner{
exec: exec,
}
}
// CreateSet creates a new set, it will ignore error when the set already exists if ignoreExistErr=true.
func (runner *runner) CreateSet(set *IPSet, ignoreExistErr bool) error {
// sets some IPSet fields if not present to their default values.
set.setIPSetDefaults()
// Validate ipset before creating
valid := set.Validate()
if !valid {
return fmt.Errorf("error creating ipset since it's invalid")
}
return runner.createSet(set, ignoreExistErr)
}
// If ignoreExistErr is set to true, then the -exist option of ipset will be specified, ipset ignores the error
// otherwise raised when the same set (setname and create parameters are identical) already exists.
func (runner *runner) createSet(set *IPSet, ignoreExistErr bool) error {
args := []string{"create", set.Name, string(set.SetType)}
if set.SetType == HashIPPortIP || set.SetType == HashIPPort || set.SetType == HashIPPortNet {
args = append(args,
"family", set.HashFamily,
"hashsize", strconv.Itoa(set.HashSize),
"maxelem", strconv.Itoa(set.MaxElem),
)
}
if set.SetType == BitmapPort {
args = append(args, "range", set.PortRange)
}
if ignoreExistErr {
args = append(args, "-exist")
}
if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil {
return fmt.Errorf("error creating ipset %s, error: %v", set.Name, err)
}
return nil
}
// AddEntry adds a new entry to the named set.
// If the -exist option is specified, ipset ignores the error otherwise raised when
// the same set (setname and create parameters are identical) already exists.
func (runner *runner) AddEntry(entry string, set *IPSet, ignoreExistErr bool) error {
args := []string{"add", set.Name, entry}
if ignoreExistErr {
args = append(args, "-exist")
}
if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil {
return fmt.Errorf("error adding entry %s, error: %v", entry, err)
}
return nil
}
// DelEntry is used to delete the specified entry from the set.
func (runner *runner) DelEntry(entry string, set string) error {
if _, err := runner.exec.Command(IPSetCmd, "del", set, entry).CombinedOutput(); err != nil {
return fmt.Errorf("error deleting entry %s: from set: %s, error: %v", entry, set, err)
}
return nil
}
// TestEntry is used to check whether the specified entry is in the set or not.
func (runner *runner) TestEntry(entry string, set string) (bool, error) {
if out, err := runner.exec.Command(IPSetCmd, "test", set, entry).CombinedOutput(); err == nil {
reg, e := regexp.Compile("NOT")
if e == nil && reg.MatchString(string(out)) {
return false, nil
} else if e == nil {
return true, nil
} else {
return false, fmt.Errorf("error testing entry: %s, error: %v", entry, e)
}
} else {
return false, fmt.Errorf("error testing entry %s: %v (%s)", entry, err, out)
}
}
// FlushSet deletes all entries from a named set.
func (runner *runner) FlushSet(set string) error {
if _, err := runner.exec.Command(IPSetCmd, "flush", set).CombinedOutput(); err != nil {
return fmt.Errorf("error flushing set: %s, error: %v", set, err)
}
return nil
}
// DestroySet is used to destroy a named set.
func (runner *runner) DestroySet(set string) error {
if out, err := runner.exec.Command(IPSetCmd, "destroy", set).CombinedOutput(); err != nil {
return fmt.Errorf("error destroying set %s, error: %v(%s)", set, err, out)
}
return nil
}
// DestroyAllSets is used to destroy all sets.
func (runner *runner) DestroyAllSets() error {
if _, err := runner.exec.Command(IPSetCmd, "destroy").CombinedOutput(); err != nil {
return fmt.Errorf("error destroying all sets, error: %v", err)
}
return nil
}
// ListSets list all set names from kernel
func (runner *runner) ListSets() ([]string, error) {
out, err := runner.exec.Command(IPSetCmd, "list", "-n").CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error listing all sets, error: %v", err)
}
return strings.Split(string(out), "\n"), nil
}
// ListEntries lists all the entries from a named set.
func (runner *runner) ListEntries(set string) ([]string, error) {
if len(set) == 0 {
return nil, fmt.Errorf("set name can't be nil")
}
out, err := runner.exec.Command(IPSetCmd, "list", set).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error listing set: %s, error: %v", set, err)
}
memberMatcher := regexp.MustCompile(EntryMemberPattern)
list := memberMatcher.ReplaceAllString(string(out[:]), "")
strs := strings.Split(list, "\n")
results := make([]string, 0)
for i := range strs {
if len(strs[i]) > 0 {
results = append(results, strs[i])
}
}
return results, nil
}
// GetVersion returns the version string.
func (runner *runner) GetVersion() (string, error) {
return getIPSetVersionString(runner.exec)
}
// getIPSetVersionString runs "ipset --version" to get the version string
// in the form of "X.Y", i.e "6.19"
func getIPSetVersionString(exec utilexec.Interface) (string, error) {
cmd := exec.Command(IPSetCmd, "--version")
cmd.SetStdin(bytes.NewReader([]byte{}))
bytes, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
versionMatcher := regexp.MustCompile(VersionPattern)
match := versionMatcher.FindStringSubmatch(string(bytes))
if match == nil {
return "", fmt.Errorf("no ipset version found in string: %s", bytes)
}
return match[0], nil
}
// checks if port range is valid. The begin port number is not necessarily less than
// end port number - ipset util can accept it. It means both 1-100 and 100-1 are valid.
func validatePortRange(portRange string) bool {
strs := strings.Split(portRange, "-")
if len(strs) != 2 {
klog.Errorf("port range should be in the format of `a-b`")
return false
}
for i := range strs {
num, err := strconv.Atoi(strs[i])
if err != nil {
klog.Errorf("Failed to parse %s, error: %v", strs[i], err)
return false
}
if num < 0 {
klog.Errorf("port number %d should be >=0", num)
return false
}
}
return true
}
// checks if the given ipset type is valid.
func validateIPSetType(set Type) bool {
for _, valid := range ValidIPSetTypes {
if set == valid {
return true
}
}
klog.Errorf("Currently supported ipset types are: %v, %s is not supported", ValidIPSetTypes, set)
return false
}
// checks if given hash family is supported in ipset
func validateHashFamily(family string) bool {
if family == ProtocolFamilyIPV4 || family == ProtocolFamilyIPV6 {
return true
}
klog.Errorf("Currently supported ip set hash families are: [%s, %s], %s is not supported", ProtocolFamilyIPV4, ProtocolFamilyIPV6, family)
return false
}
// IsNotFoundError returns true if the error indicates "not found". It parses
// the error string looking for known values, which is imperfect but works in
// practice.
func IsNotFoundError(err error) bool {
es := err.Error()
if strings.Contains(es, "does not exist") {
// set with the same name already exists
// xref: https://github.com/Olipro/ipset/blob/master/lib/errcode.c#L32-L33
return true
}
if strings.Contains(es, "element is missing") {
// entry is missing from the set
// xref: https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1904
// https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1925
return true
}
return false
}
// checks if given protocol is supported in entry
func validateProtocol(protocol string) bool {
if protocol == ProtocolTCP || protocol == ProtocolUDP || protocol == ProtocolSCTP {
return true
}
klog.Errorf("Invalid entry's protocol: %s, supported protocols are [%s, %s, %s]", protocol, ProtocolTCP, ProtocolUDP, ProtocolSCTP)
return false
}
// parsePortRange parse the begin and end port from a raw string(format: a-b). beginPort <= endPort
// in the return value.
func parsePortRange(portRange string) (beginPort int, endPort int, err error) {
if len(portRange) == 0 {
portRange = DefaultPortRange
}
strs := strings.Split(portRange, "-")
if len(strs) != 2 {
// port number -1 indicates invalid
return -1, -1, fmt.Errorf("port range should be in the format of `a-b`")
}
for i := range strs {
num, err := strconv.Atoi(strs[i])
if err != nil {
// port number -1 indicates invalid
return -1, -1, err
}
if num < 0 {
// port number -1 indicates invalid
return -1, -1, fmt.Errorf("port number %d should be >=0", num)
}
if i == 0 {
beginPort = num
continue
}
endPort = num
// switch when first port number > second port number
if beginPort > endPort {
endPort = beginPort
beginPort = num
}
}
return beginPort, endPort, nil
}
var _ = Interface(&runner{})

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["fake.go"],
importpath = "k8s.io/kubernetes/pkg/util/ipset/testing",
visibility = ["//visibility:public"],
deps = [
"//pkg/util/ipset:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["fake_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/util/ipset:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)

View File

@ -1,143 +0,0 @@
/*
Copyright 2017 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 testing
import (
"fmt"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/ipset"
)
// FakeIPSet is a no-op implementation of ipset Interface
type FakeIPSet struct {
// version of ipset util
Version string
// The key of Sets map is the ip set name
Sets map[string]*ipset.IPSet
// The key of Entries map is the ip set name where the entries exists
Entries map[string]sets.String
}
// NewFake create a new fake ipset interface - it initialize the FakeIPSet.
func NewFake(version string) *FakeIPSet {
return &FakeIPSet{
Version: version,
Sets: make(map[string]*ipset.IPSet),
Entries: make(map[string]sets.String),
}
}
// GetVersion is part of interface.
func (f *FakeIPSet) GetVersion() (string, error) {
return f.Version, nil
}
// FlushSet is part of interface. It deletes all entries from a named set but keeps the set itself.
func (f *FakeIPSet) FlushSet(set string) error {
if f.Entries == nil {
return fmt.Errorf("entries map can't be nil")
}
// delete all entry elements
for true {
if _, has := f.Entries[set].PopAny(); has {
continue
}
break
}
return nil
}
// DestroySet is part of interface. It deletes both the entries and the set itself.
func (f *FakeIPSet) DestroySet(set string) error {
delete(f.Sets, set)
delete(f.Entries, set)
return nil
}
// DestroyAllSets is part of interface.
func (f *FakeIPSet) DestroyAllSets() error {
f.Sets = nil
f.Entries = nil
return nil
}
// CreateSet is part of interface.
func (f *FakeIPSet) CreateSet(set *ipset.IPSet, ignoreExistErr bool) error {
if f.Sets[set.Name] != nil {
if !ignoreExistErr {
// already exists
return fmt.Errorf("Set cannot be created: set with the same name already exists")
}
return nil
}
f.Sets[set.Name] = set
// initialize entry map
f.Entries[set.Name] = sets.NewString()
return nil
}
// AddEntry is part of interface.
func (f *FakeIPSet) AddEntry(entry string, set *ipset.IPSet, ignoreExistErr bool) error {
if f.Entries[set.Name].Has(entry) {
if !ignoreExistErr {
// already exists
return fmt.Errorf("Element cannot be added to the set: it's already added")
}
return nil
}
f.Entries[set.Name].Insert(entry)
return nil
}
// DelEntry is part of interface.
func (f *FakeIPSet) DelEntry(entry string, set string) error {
if f.Entries == nil {
return fmt.Errorf("entries map can't be nil")
}
f.Entries[set].Delete(entry)
return nil
}
// TestEntry is part of interface.
func (f *FakeIPSet) TestEntry(entry string, set string) (bool, error) {
if f.Entries == nil {
return false, fmt.Errorf("entries map can't be nil")
}
found := f.Entries[set].Has(entry)
return found, nil
}
// ListEntries is part of interface.
func (f *FakeIPSet) ListEntries(set string) ([]string, error) {
if f.Entries == nil {
return nil, fmt.Errorf("entries map can't be nil")
}
return f.Entries[set].UnsortedList(), nil
}
// ListSets is part of interface.
func (f *FakeIPSet) ListSets() ([]string, error) {
res := []string{}
for set := range f.Sets {
res = append(res, set)
}
return res, nil
}
var _ = ipset.Interface(&FakeIPSet{})

View File

@ -1,152 +0,0 @@
/*
Copyright 2017 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 testing
import (
"testing"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/ipset"
)
const testVersion = "v6.19"
func TestSetEntry(t *testing.T) {
fake := NewFake(testVersion)
version, err := fake.GetVersion()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if version != testVersion {
t.Errorf("Unexpected version mismatch, expected: %s, got: %s", testVersion, version)
}
// create a set
set := &ipset.IPSet{
Name: "foo",
SetType: ipset.HashIPPort,
HashFamily: ipset.ProtocolFamilyIPV4,
}
if err := fake.CreateSet(set, true); err != nil {
t.Errorf("Unexpected error: %v", err)
}
// add two entries
fake.AddEntry("192.168.1.1,tcp:8080", set, true)
fake.AddEntry("192.168.1.2,tcp:8081", set, true)
entries, err := fake.ListEntries(set.Name)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(entries) != 2 {
t.Errorf("Expected 2 entries, got %d", len(entries))
}
expectedEntries := sets.NewString("192.168.1.1,tcp:8080", "192.168.1.2,tcp:8081")
if !expectedEntries.Equal(sets.NewString(entries...)) {
t.Errorf("Unexpected entries mismatch, expected: %v, got: %v", expectedEntries, entries)
}
// test entries
found, err := fake.TestEntry("192.168.1.1,tcp:8080", set.Name)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !found {
t.Errorf("Unexpected entry 192.168.1.1,tcp:8080 not found")
}
found, err = fake.TestEntry("192.168.1.2,tcp:8081", set.Name)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !found {
t.Errorf("Unexpected entry 192.168.1.2,tcp:8081 not found")
}
// delete entry from a given set
if err := fake.DelEntry("192.168.1.1,tcp:8080", set.Name); err != nil {
t.Errorf("Unexpected error: %v", err)
}
entries, err = fake.ListEntries(set.Name)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(entries) != 1 {
t.Errorf("Expected 1 entries, got %d", len(entries))
}
expectedEntries = sets.NewString("192.168.1.2,tcp:8081")
if !expectedEntries.Equal(sets.NewString(entries...)) {
t.Errorf("Unexpected entries mismatch, expected: %v, got: %v", expectedEntries, entries)
}
// Flush set
if err := fake.FlushSet(set.Name); err != nil {
t.Errorf("Unexpected error: %v", err)
}
entries, err = fake.ListEntries(set.Name)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(entries) != 0 {
t.Errorf("Expected 0 entries, got %d, entries: %v", len(entries), entries)
}
// create another set
set2 := &ipset.IPSet{
Name: "bar",
SetType: ipset.HashIPPortIP,
HashFamily: ipset.ProtocolFamilyIPV6,
}
if err := fake.CreateSet(set2, true); err != nil {
t.Errorf("Unexpected error: %v", err)
}
setList, err := fake.ListSets()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(setList) != 2 {
t.Errorf("Expected 2 sets, got %d", len(setList))
}
expectedSets := sets.NewString("foo", "bar")
if !expectedSets.Equal(sets.NewString(setList...)) {
t.Errorf("Unexpected sets mismatch, expected: %v, got: %v", expectedSets, setList)
}
// Destroy a given set
if err := fake.DestroySet(set.Name); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if fake.Sets[set.Name] != nil {
t.Errorf("Unexpected set: %v", fake.Sets[set.Name])
}
if fake.Entries[set.Name] != nil {
t.Errorf("Unexpected entries: %v", fake.Entries[set.Name])
}
// Destroy all sets
if err := fake.DestroyAllSets(); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(fake.Sets) != 0 {
t.Errorf("Expected 0 sets, got %d, sets: %v", len(fake.Sets), fake.Sets)
}
if len(fake.Entries) != 0 {
t.Errorf("Expected 0 entries, got %d, entries: %v", len(fake.Entries), fake.Entries)
}
}
// TODO: Test ignoreExistErr=false

View File

@ -1,62 +0,0 @@
/*
Copyright 2017 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 ipset
// Type represents the ipset type
type Type string
const (
// HashIPPort represents the `hash:ip,port` type ipset. The hash:ip,port is similar to hash:ip but
// you can store IP address and protocol-port pairs in it. TCP, SCTP, UDP, UDPLITE, ICMP and ICMPv6 are supported
// with port numbers/ICMP(v6) types and other protocol numbers without port information.
HashIPPort Type = "hash:ip,port"
// HashIPPortIP represents the `hash:ip,port,ip` type ipset. The hash:ip,port,ip set type uses a hash to store
// IP address, port number and a second IP address triples. The port number is interpreted together with a
// protocol (default TCP) and zero protocol number cannot be used.
HashIPPortIP Type = "hash:ip,port,ip"
// HashIPPortNet represents the `hash:ip,port,net` type ipset. The hash:ip,port,net set type uses a hash to store IP address, port number and IP network address triples. The port
// number is interpreted together with a protocol (default TCP) and zero protocol number cannot be used. Network address
// with zero prefix size cannot be stored either.
HashIPPortNet Type = "hash:ip,port,net"
// BitmapPort represents the `bitmap:port` type ipset. The bitmap:port set type uses a memory range, where each bit
// represents one TCP/UDP port. A bitmap:port type of set can store up to 65535 ports.
BitmapPort Type = "bitmap:port"
)
// DefaultPortRange defines the default bitmap:port valid port range.
const DefaultPortRange string = "0-65535"
const (
// ProtocolFamilyIPV4 represents IPv4 protocol.
ProtocolFamilyIPV4 = "inet"
// ProtocolFamilyIPV6 represents IPv6 protocol.
ProtocolFamilyIPV6 = "inet6"
// ProtocolTCP represents TCP protocol.
ProtocolTCP = "tcp"
// ProtocolUDP represents UDP protocol.
ProtocolUDP = "udp"
// ProtocolSCTP represents SCTP protocol.
ProtocolSCTP = "sctp"
)
// ValidIPSetTypes defines the supported ip set type.
var ValidIPSetTypes = []Type{
HashIPPort,
HashIPPortIP,
BitmapPort,
HashIPPortNet,
}

View File

@ -1,69 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"iptables.go",
"iptables_linux.go",
"iptables_unsupported.go",
"save_restore.go",
],
importpath = "k8s.io/kubernetes/pkg/util/iptables",
deps = [
"//pkg/util/dbus:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/trace:go_default_library",
"//vendor/github.com/godbus/dbus:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux": [
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"//conditions:default": [],
}),
)
go_test(
name = "go_default_test",
srcs = [
"iptables_test.go",
"save_restore_test.go",
],
embed = [":go_default_library"],
deps = select({
"@io_bazel_rules_go//go/platform:linux": [
"//pkg/util/dbus:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/util/iptables/testing:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -1,9 +0,0 @@
reviewers:
- dcbw
- thockin
- eparis
- danwinship
approvers:
- dcbw
- thockin
- eparis

View File

@ -1,18 +0,0 @@
/*
Copyright 2014 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 iptables provides an interface and implementations for running iptables commands.
package iptables // import "k8s.io/kubernetes/pkg/util/iptables"

View File

@ -1,713 +0,0 @@
/*
Copyright 2014 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 iptables
import (
"bytes"
"context"
"fmt"
"regexp"
"strings"
"sync"
"time"
godbus "github.com/godbus/dbus"
"k8s.io/apimachinery/pkg/util/sets"
utilversion "k8s.io/apimachinery/pkg/util/version"
utiltrace "k8s.io/apiserver/pkg/util/trace"
"k8s.io/klog"
utildbus "k8s.io/kubernetes/pkg/util/dbus"
utilexec "k8s.io/utils/exec"
)
type RulePosition string
const (
Prepend RulePosition = "-I"
Append RulePosition = "-A"
)
// An injectable interface for running iptables commands. Implementations must be goroutine-safe.
type Interface interface {
// GetVersion returns the "X.Y.Z" version string for iptables.
GetVersion() (string, error)
// EnsureChain checks if the specified chain exists and, if not, creates it. If the chain existed, return true.
EnsureChain(table Table, chain Chain) (bool, error)
// FlushChain clears the specified chain. If the chain did not exist, return error.
FlushChain(table Table, chain Chain) error
// DeleteChain deletes the specified chain. If the chain did not exist, return error.
DeleteChain(table Table, chain Chain) error
// EnsureRule checks if the specified rule is present and, if not, creates it. If the rule existed, return true.
EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error)
// DeleteRule checks if the specified rule is present and, if so, deletes it.
DeleteRule(table Table, chain Chain, args ...string) error
// IsIpv6 returns true if this is managing ipv6 tables
IsIpv6() bool
// SaveInto calls `iptables-save` for table and stores result in a given buffer.
SaveInto(table Table, buffer *bytes.Buffer) error
// Restore runs `iptables-restore` passing data through []byte.
// table is the Table to restore
// data should be formatted like the output of SaveInto()
// flush sets the presence of the "--noflush" flag. see: FlushFlag
// counters sets the "--counters" flag. see: RestoreCountersFlag
Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error
// RestoreAll is the same as Restore except that no table is specified.
RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error
// AddReloadFunc adds a function to call on iptables reload
AddReloadFunc(reloadFunc func())
// Destroy cleans up resources used by the Interface
Destroy()
}
type Protocol byte
const (
ProtocolIpv4 Protocol = iota + 1
ProtocolIpv6
)
type Table string
const (
TableNAT Table = "nat"
TableFilter Table = "filter"
TableMangle Table = "mangle"
)
type Chain string
const (
ChainPostrouting Chain = "POSTROUTING"
ChainPrerouting Chain = "PREROUTING"
ChainOutput Chain = "OUTPUT"
ChainInput Chain = "INPUT"
ChainForward Chain = "FORWARD"
)
const (
cmdIPTablesSave string = "iptables-save"
cmdIPTablesRestore string = "iptables-restore"
cmdIPTables string = "iptables"
cmdIP6TablesRestore string = "ip6tables-restore"
cmdIP6TablesSave string = "ip6tables-save"
cmdIP6Tables string = "ip6tables"
)
// Option flag for Restore
type RestoreCountersFlag bool
const RestoreCounters RestoreCountersFlag = true
const NoRestoreCounters RestoreCountersFlag = false
// Option flag for Flush
type FlushFlag bool
const FlushTables FlushFlag = true
const NoFlushTables FlushFlag = false
// Versions of iptables less than this do not support the -C / --check flag
// (test whether a rule exists).
const MinCheckVersion = "1.4.11"
// Minimum iptables versions supporting the -w and -w<seconds> flags
const WaitMinVersion = "1.4.20"
const WaitSecondsMinVersion = "1.4.22"
const WaitString = "-w"
const WaitSecondsValue = "5"
const LockfilePath16x = "/run/xtables.lock"
// runner implements Interface in terms of exec("iptables").
type runner struct {
mu sync.Mutex
exec utilexec.Interface
dbus utildbus.Interface
protocol Protocol
hasCheck bool
hasListener bool
waitFlag []string
restoreWaitFlag []string
lockfilePath string
reloadFuncs []func()
signal chan *godbus.Signal
}
// newInternal returns a new Interface which will exec iptables, and allows the
// caller to change the iptables-restore lockfile path
func newInternal(exec utilexec.Interface, dbus utildbus.Interface, protocol Protocol, lockfilePath string) Interface {
vstring, err := getIPTablesVersionString(exec, protocol)
if err != nil {
klog.Warningf("Error checking iptables version, assuming version at least %s: %v", MinCheckVersion, err)
vstring = MinCheckVersion
}
if lockfilePath == "" {
lockfilePath = LockfilePath16x
}
runner := &runner{
exec: exec,
dbus: dbus,
protocol: protocol,
hasCheck: getIPTablesHasCheckCommand(vstring),
hasListener: false,
waitFlag: getIPTablesWaitFlag(vstring),
restoreWaitFlag: getIPTablesRestoreWaitFlag(exec, protocol),
lockfilePath: lockfilePath,
}
return runner
}
// New returns a new Interface which will exec iptables.
func New(exec utilexec.Interface, dbus utildbus.Interface, protocol Protocol) Interface {
return newInternal(exec, dbus, protocol, "")
}
// Destroy is part of Interface.
func (runner *runner) Destroy() {
if runner.signal != nil {
runner.signal <- nil
}
}
const (
firewalldName = "org.fedoraproject.FirewallD1"
firewalldPath = "/org/fedoraproject/FirewallD1"
firewalldInterface = "org.fedoraproject.FirewallD1"
)
// Connects to D-Bus and listens for FirewallD start/restart. (On non-FirewallD-using
// systems, this is effectively a no-op; we listen for the signals, but they will never be
// emitted, so reload() will never be called.)
func (runner *runner) connectToFirewallD() {
bus, err := runner.dbus.SystemBus()
if err != nil {
klog.V(1).Infof("Could not connect to D-Bus system bus: %s", err)
return
}
runner.hasListener = true
rule := fmt.Sprintf("type='signal',sender='%s',path='%s',interface='%s',member='Reloaded'", firewalldName, firewalldPath, firewalldInterface)
bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", firewalldName)
bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
runner.signal = make(chan *godbus.Signal, 10)
bus.Signal(runner.signal)
go runner.dbusSignalHandler(bus)
}
// GetVersion returns the version string.
func (runner *runner) GetVersion() (string, error) {
return getIPTablesVersionString(runner.exec, runner.protocol)
}
// EnsureChain is part of Interface.
func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) {
fullArgs := makeFullArgs(table, chain)
runner.mu.Lock()
defer runner.mu.Unlock()
out, err := runner.run(opCreateChain, fullArgs)
if err != nil {
if ee, ok := err.(utilexec.ExitError); ok {
if ee.Exited() && ee.ExitStatus() == 1 {
return true, nil
}
}
return false, fmt.Errorf("error creating chain %q: %v: %s", chain, err, out)
}
return false, nil
}
// FlushChain is part of Interface.
func (runner *runner) FlushChain(table Table, chain Chain) error {
fullArgs := makeFullArgs(table, chain)
runner.mu.Lock()
defer runner.mu.Unlock()
out, err := runner.run(opFlushChain, fullArgs)
if err != nil {
return fmt.Errorf("error flushing chain %q: %v: %s", chain, err, out)
}
return nil
}
// DeleteChain is part of Interface.
func (runner *runner) DeleteChain(table Table, chain Chain) error {
fullArgs := makeFullArgs(table, chain)
runner.mu.Lock()
defer runner.mu.Unlock()
// TODO: we could call iptables -S first, ignore the output and check for non-zero return (more like DeleteRule)
out, err := runner.run(opDeleteChain, fullArgs)
if err != nil {
return fmt.Errorf("error deleting chain %q: %v: %s", chain, err, out)
}
return nil
}
// EnsureRule is part of Interface.
func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) {
fullArgs := makeFullArgs(table, chain, args...)
runner.mu.Lock()
defer runner.mu.Unlock()
exists, err := runner.checkRule(table, chain, args...)
if err != nil {
return false, err
}
if exists {
return true, nil
}
out, err := runner.run(operation(position), fullArgs)
if err != nil {
return false, fmt.Errorf("error appending rule: %v: %s", err, out)
}
return false, nil
}
// DeleteRule is part of Interface.
func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error {
fullArgs := makeFullArgs(table, chain, args...)
runner.mu.Lock()
defer runner.mu.Unlock()
exists, err := runner.checkRule(table, chain, args...)
if err != nil {
return err
}
if !exists {
return nil
}
out, err := runner.run(opDeleteRule, fullArgs)
if err != nil {
return fmt.Errorf("error deleting rule: %v: %s", err, out)
}
return nil
}
func (runner *runner) IsIpv6() bool {
return runner.protocol == ProtocolIpv6
}
// SaveInto is part of Interface.
func (runner *runner) SaveInto(table Table, buffer *bytes.Buffer) error {
runner.mu.Lock()
defer runner.mu.Unlock()
trace := utiltrace.New("iptables save")
defer trace.LogIfLong(2 * time.Second)
// run and return
iptablesSaveCmd := iptablesSaveCommand(runner.protocol)
args := []string{"-t", string(table)}
klog.V(4).Infof("running %s %v", iptablesSaveCmd, args)
cmd := runner.exec.Command(iptablesSaveCmd, args...)
// Since CombinedOutput() doesn't support redirecting it to a buffer,
// we need to workaround it by redirecting stdout and stderr to buffer
// and explicitly calling Run() [CombinedOutput() underneath itself
// creates a new buffer, redirects stdout and stderr to it and also
// calls Run()].
cmd.SetStdout(buffer)
cmd.SetStderr(buffer)
return cmd.Run()
}
// Restore is part of Interface.
func (runner *runner) Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
// setup args
args := []string{"-T", string(table)}
return runner.restoreInternal(args, data, flush, counters)
}
// RestoreAll is part of Interface.
func (runner *runner) RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
// setup args
args := make([]string, 0)
return runner.restoreInternal(args, data, flush, counters)
}
type iptablesLocker interface {
Close() error
}
// restoreInternal is the shared part of Restore/RestoreAll
func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
runner.mu.Lock()
defer runner.mu.Unlock()
trace := utiltrace.New("iptables restore")
defer trace.LogIfLong(2 * time.Second)
if !flush {
args = append(args, "--noflush")
}
if counters {
args = append(args, "--counters")
}
// Grab the iptables lock to prevent iptables-restore and iptables
// from stepping on each other. iptables-restore 1.6.2 will have
// a --wait option like iptables itself, but that's not widely deployed.
if len(runner.restoreWaitFlag) == 0 {
locker, err := grabIptablesLocks(runner.lockfilePath)
if err != nil {
return err
}
trace.Step("Locks grabbed")
defer func(locker iptablesLocker) {
if err := locker.Close(); err != nil {
klog.Errorf("Failed to close iptables locks: %v", err)
}
}(locker)
}
// run the command and return the output or an error including the output and error
fullArgs := append(runner.restoreWaitFlag, args...)
iptablesRestoreCmd := iptablesRestoreCommand(runner.protocol)
klog.V(4).Infof("running %s %v", iptablesRestoreCmd, fullArgs)
cmd := runner.exec.Command(iptablesRestoreCmd, fullArgs...)
cmd.SetStdin(bytes.NewBuffer(data))
b, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%v (%s)", err, b)
}
return nil
}
func iptablesSaveCommand(protocol Protocol) string {
if protocol == ProtocolIpv6 {
return cmdIP6TablesSave
} else {
return cmdIPTablesSave
}
}
func iptablesRestoreCommand(protocol Protocol) string {
if protocol == ProtocolIpv6 {
return cmdIP6TablesRestore
} else {
return cmdIPTablesRestore
}
}
func iptablesCommand(protocol Protocol) string {
if protocol == ProtocolIpv6 {
return cmdIP6Tables
} else {
return cmdIPTables
}
}
func (runner *runner) run(op operation, args []string) ([]byte, error) {
return runner.runContext(nil, op, args)
}
func (runner *runner) runContext(ctx context.Context, op operation, args []string) ([]byte, error) {
iptablesCmd := iptablesCommand(runner.protocol)
fullArgs := append(runner.waitFlag, string(op))
fullArgs = append(fullArgs, args...)
klog.V(5).Infof("running iptables %s %v", string(op), args)
if ctx == nil {
return runner.exec.Command(iptablesCmd, fullArgs...).CombinedOutput()
}
return runner.exec.CommandContext(ctx, iptablesCmd, fullArgs...).CombinedOutput()
// Don't log err here - callers might not think it is an error.
}
// Returns (bool, nil) if it was able to check the existence of the rule, or
// (<undefined>, error) if the process of checking failed.
func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) {
if runner.hasCheck {
return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...))
}
return runner.checkRuleWithoutCheck(table, chain, args...)
}
var hexnumRE = regexp.MustCompile("0x0+([0-9])")
func trimhex(s string) string {
return hexnumRE.ReplaceAllString(s, "0x$1")
}
// Executes the rule check without using the "-C" flag, instead parsing iptables-save.
// Present for compatibility with <1.4.11 versions of iptables. This is full
// of hack and half-measures. We should nix this ASAP.
func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) {
iptablesSaveCmd := iptablesSaveCommand(runner.protocol)
klog.V(1).Infof("running %s -t %s", iptablesSaveCmd, string(table))
out, err := runner.exec.Command(iptablesSaveCmd, "-t", string(table)).CombinedOutput()
if err != nil {
return false, fmt.Errorf("error checking rule: %v", err)
}
// Sadly, iptables has inconsistent quoting rules for comments. Just remove all quotes.
// Also, quoted multi-word comments (which are counted as a single arg)
// will be unpacked into multiple args,
// in order to compare against iptables-save output (which will be split at whitespace boundary)
// e.g. a single arg('"this must be before the NodePort rules"') will be unquoted and unpacked into 7 args.
var argsCopy []string
for i := range args {
tmpField := strings.Trim(args[i], "\"")
tmpField = trimhex(tmpField)
argsCopy = append(argsCopy, strings.Fields(tmpField)...)
}
argset := sets.NewString(argsCopy...)
for _, line := range strings.Split(string(out), "\n") {
var fields = strings.Fields(line)
// Check that this is a rule for the correct chain, and that it has
// the correct number of argument (+2 for "-A <chain name>")
if !strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) || len(fields) != len(argsCopy)+2 {
continue
}
// Sadly, iptables has inconsistent quoting rules for comments.
// Just remove all quotes.
for i := range fields {
fields[i] = strings.Trim(fields[i], "\"")
fields[i] = trimhex(fields[i])
}
// TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar"
if sets.NewString(fields...).IsSuperset(argset) {
return true, nil
}
klog.V(5).Infof("DBG: fields is not a superset of args: fields=%v args=%v", fields, args)
}
return false, nil
}
// Executes the rule check using the "-C" flag
func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
out, err := runner.runContext(ctx, opCheckRule, args)
if ctx.Err() == context.DeadlineExceeded {
return false, fmt.Errorf("timed out while checking rules")
}
if err == nil {
return true, nil
}
if ee, ok := err.(utilexec.ExitError); ok {
// iptables uses exit(1) to indicate a failure of the operation,
// as compared to a malformed commandline, for example.
if ee.Exited() && ee.ExitStatus() == 1 {
return false, nil
}
}
return false, fmt.Errorf("error checking rule: %v: %s", err, out)
}
type operation string
const (
opCreateChain operation = "-N"
opFlushChain operation = "-F"
opDeleteChain operation = "-X"
opAppendRule operation = "-A"
opCheckRule operation = "-C"
opDeleteRule operation = "-D"
)
func makeFullArgs(table Table, chain Chain, args ...string) []string {
return append([]string{string(chain), "-t", string(table)}, args...)
}
// Checks if iptables has the "-C" flag
func getIPTablesHasCheckCommand(vstring string) bool {
minVersion, err := utilversion.ParseGeneric(MinCheckVersion)
if err != nil {
klog.Errorf("MinCheckVersion (%s) is not a valid version string: %v", MinCheckVersion, err)
return true
}
version, err := utilversion.ParseGeneric(vstring)
if err != nil {
klog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err)
return true
}
return version.AtLeast(minVersion)
}
// Checks if iptables version has a "wait" flag
func getIPTablesWaitFlag(vstring string) []string {
version, err := utilversion.ParseGeneric(vstring)
if err != nil {
klog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err)
return nil
}
minVersion, err := utilversion.ParseGeneric(WaitMinVersion)
if err != nil {
klog.Errorf("WaitMinVersion (%s) is not a valid version string: %v", WaitMinVersion, err)
return nil
}
if version.LessThan(minVersion) {
return nil
}
minVersion, err = utilversion.ParseGeneric(WaitSecondsMinVersion)
if err != nil {
klog.Errorf("WaitSecondsMinVersion (%s) is not a valid version string: %v", WaitSecondsMinVersion, err)
return nil
}
if version.LessThan(minVersion) {
return []string{WaitString}
} else {
return []string{WaitString, WaitSecondsValue}
}
}
// getIPTablesVersionString runs "iptables --version" to get the version string
// in the form "X.X.X"
func getIPTablesVersionString(exec utilexec.Interface, protocol Protocol) (string, error) {
// this doesn't access mutable state so we don't need to use the interface / runner
iptablesCmd := iptablesCommand(protocol)
bytes, err := exec.Command(iptablesCmd, "--version").CombinedOutput()
if err != nil {
return "", err
}
versionMatcher := regexp.MustCompile("v([0-9]+(\\.[0-9]+)+)")
match := versionMatcher.FindStringSubmatch(string(bytes))
if match == nil {
return "", fmt.Errorf("no iptables version found in string: %s", bytes)
}
return match[1], nil
}
// Checks if iptables-restore has a "wait" flag
// --wait support landed in v1.6.1+ right before --version support, so
// any version of iptables-restore that supports --version will also
// support --wait
func getIPTablesRestoreWaitFlag(exec utilexec.Interface, protocol Protocol) []string {
vstring, err := getIPTablesRestoreVersionString(exec, protocol)
if err != nil || vstring == "" {
klog.V(3).Infof("couldn't get iptables-restore version; assuming it doesn't support --wait")
return nil
}
if _, err := utilversion.ParseGeneric(vstring); err != nil {
klog.V(3).Infof("couldn't parse iptables-restore version; assuming it doesn't support --wait")
return nil
}
return []string{WaitString, WaitSecondsValue}
}
// getIPTablesRestoreVersionString runs "iptables-restore --version" to get the version string
// in the form "X.X.X"
func getIPTablesRestoreVersionString(exec utilexec.Interface, protocol Protocol) (string, error) {
// this doesn't access mutable state so we don't need to use the interface / runner
// iptables-restore hasn't always had --version, and worse complains
// about unrecognized commands but doesn't exit when it gets them.
// Work around that by setting stdin to nothing so it exits immediately.
iptablesRestoreCmd := iptablesRestoreCommand(protocol)
cmd := exec.Command(iptablesRestoreCmd, "--version")
cmd.SetStdin(bytes.NewReader([]byte{}))
bytes, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
versionMatcher := regexp.MustCompile("v([0-9]+(\\.[0-9]+)+)")
match := versionMatcher.FindStringSubmatch(string(bytes))
if match == nil {
return "", fmt.Errorf("no iptables version found in string: %s", bytes)
}
return match[1], nil
}
// goroutine to listen for D-Bus signals
func (runner *runner) dbusSignalHandler(bus utildbus.Connection) {
firewalld := bus.Object(firewalldName, firewalldPath)
for s := range runner.signal {
if s == nil {
// Unregister
bus.Signal(runner.signal)
return
}
switch s.Name {
case "org.freedesktop.DBus.NameOwnerChanged":
name := s.Body[0].(string)
new_owner := s.Body[2].(string)
if name != firewalldName || len(new_owner) == 0 {
continue
}
// FirewallD startup (specifically the part where it deletes
// all existing iptables rules) may not yet be complete when
// we get this signal, so make a dummy request to it to
// synchronize.
firewalld.Call(firewalldInterface+".getDefaultZone", 0)
runner.reload()
case firewalldInterface + ".Reloaded":
runner.reload()
}
}
}
// AddReloadFunc is part of Interface
func (runner *runner) AddReloadFunc(reloadFunc func()) {
runner.mu.Lock()
defer runner.mu.Unlock()
// We only need to listen to firewalld if there are Reload functions, so lazy
// initialize the listener.
if !runner.hasListener {
runner.connectToFirewallD()
}
runner.reloadFuncs = append(runner.reloadFuncs, reloadFunc)
}
// runs all reload funcs to re-sync iptables rules
func (runner *runner) reload() {
klog.V(1).Infof("reloading iptables rules")
for _, f := range runner.reloadFuncs {
f()
}
}
// IsNotFoundError returns true if the error indicates "not found". It parses
// the error string looking for known values, which is imperfect but works in
// practice.
func IsNotFoundError(err error) bool {
es := err.Error()
if strings.Contains(es, "No such file or directory") {
return true
}
if strings.Contains(es, "No chain/target/match by that name") {
return true
}
return false
}

View File

@ -1,100 +0,0 @@
// +build linux
/*
Copyright 2017 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 iptables
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/sys/unix"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/wait"
)
type locker struct {
lock16 *os.File
lock14 *net.UnixListener
}
func (l *locker) Close() error {
errList := []error{}
if l.lock16 != nil {
if err := l.lock16.Close(); err != nil {
errList = append(errList, err)
}
}
if l.lock14 != nil {
if err := l.lock14.Close(); err != nil {
errList = append(errList, err)
}
}
return utilerrors.NewAggregate(errList)
}
func grabIptablesLocks(lockfilePath string) (iptablesLocker, error) {
var err error
var success bool
l := &locker{}
defer func(l *locker) {
// Clean up immediately on failure
if !success {
l.Close()
}
}(l)
// Grab both 1.6.x and 1.4.x-style locks; we don't know what the
// iptables-restore version is if it doesn't support --wait, so we
// can't assume which lock method it'll use.
// Roughly duplicate iptables 1.6.x xtables_lock() function.
l.lock16, err = os.OpenFile(lockfilePath, os.O_CREATE, 0600)
if err != nil {
return nil, fmt.Errorf("failed to open iptables lock %s: %v", lockfilePath, err)
}
if err := wait.PollImmediate(200*time.Millisecond, 2*time.Second, func() (bool, error) {
if err := grabIptablesFileLock(l.lock16); err != nil {
return false, nil
}
return true, nil
}); err != nil {
return nil, fmt.Errorf("failed to acquire new iptables lock: %v", err)
}
// Roughly duplicate iptables 1.4.x xtables_lock() function.
if err := wait.PollImmediate(200*time.Millisecond, 2*time.Second, func() (bool, error) {
l.lock14, err = net.ListenUnix("unix", &net.UnixAddr{Name: "@xtables", Net: "unix"})
if err != nil {
return false, nil
}
return true, nil
}); err != nil {
return nil, fmt.Errorf("failed to acquire old iptables lock: %v", err)
}
success = true
return l, nil
}
func grabIptablesFileLock(f *os.File) error {
return unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
// +build !linux
/*
Copyright 2017 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 iptables
import (
"fmt"
"os"
)
func grabIptablesLocks(lockfilePath string) (iptablesLocker, error) {
return nil, fmt.Errorf("iptables unsupported on this platform")
}
func grabIptablesFileLock(f *os.File) error {
return fmt.Errorf("iptables unsupported on this platform")
}

View File

@ -1,118 +0,0 @@
/*
Copyright 2014 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 iptables
import (
"bytes"
"fmt"
)
var (
commitBytes = []byte("COMMIT")
spaceBytes = []byte(" ")
)
// MakeChainLine return an iptables-save/restore formatted chain line given a Chain
func MakeChainLine(chain Chain) string {
return fmt.Sprintf(":%s - [0:0]", chain)
}
// GetChainLines parses a table's iptables-save data to find chains in the table.
// It returns a map of iptables.Chain to []byte where the []byte is the chain line
// from save (with counters etc.).
// Note that to avoid allocations memory is SHARED with save.
func GetChainLines(table Table, save []byte) map[Chain][]byte {
chainsMap := make(map[Chain][]byte)
tablePrefix := []byte("*" + string(table))
readIndex := 0
// find beginning of table
for readIndex < len(save) {
line, n := readLine(readIndex, save)
readIndex = n
if bytes.HasPrefix(line, tablePrefix) {
break
}
}
// parse table lines
for readIndex < len(save) {
line, n := readLine(readIndex, save)
readIndex = n
if len(line) == 0 {
continue
}
if bytes.HasPrefix(line, commitBytes) || line[0] == '*' {
break
} else if line[0] == '#' {
continue
} else if line[0] == ':' && len(line) > 1 {
// We assume that the <line> contains space - chain lines have 3 fields,
// space delimited. If there is no space, this line will panic.
chain := Chain(line[1:bytes.Index(line, spaceBytes)])
chainsMap[chain] = line
}
}
return chainsMap
}
func readLine(readIndex int, byteArray []byte) ([]byte, int) {
currentReadIndex := readIndex
// consume left spaces
for currentReadIndex < len(byteArray) {
if byteArray[currentReadIndex] == ' ' {
currentReadIndex++
} else {
break
}
}
// leftTrimIndex stores the left index of the line after the line is left-trimmed
leftTrimIndex := currentReadIndex
// rightTrimIndex stores the right index of the line after the line is right-trimmed
// it is set to -1 since the correct value has not yet been determined.
rightTrimIndex := -1
for ; currentReadIndex < len(byteArray); currentReadIndex++ {
if byteArray[currentReadIndex] == ' ' {
// set rightTrimIndex
if rightTrimIndex == -1 {
rightTrimIndex = currentReadIndex
}
} else if (byteArray[currentReadIndex] == '\n') || (currentReadIndex == (len(byteArray) - 1)) {
// end of line or byte buffer is reached
if currentReadIndex <= leftTrimIndex {
return nil, currentReadIndex + 1
}
// set the rightTrimIndex
if rightTrimIndex == -1 {
rightTrimIndex = currentReadIndex
if currentReadIndex == (len(byteArray)-1) && (byteArray[currentReadIndex] != '\n') {
// ensure that the last character is part of the returned string,
// unless the last character is '\n'
rightTrimIndex = currentReadIndex + 1
}
}
// Avoid unnecessary allocation.
return byteArray[leftTrimIndex:rightTrimIndex], currentReadIndex + 1
} else {
// unset rightTrimIndex
rightTrimIndex = -1
}
}
return nil, currentReadIndex
}

View File

@ -1,53 +0,0 @@
/*
Copyright 2018 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 iptables
import (
"testing"
)
func TestReadLinesFromByteBuffer(t *testing.T) {
testFn := func(byteArray []byte, expected []string) {
index := 0
readIndex := 0
for ; readIndex < len(byteArray); index++ {
line, n := readLine(readIndex, byteArray)
readIndex = n
if expected[index] != string(line) {
t.Errorf("expected:%q, actual:%q", expected[index], line)
}
} // for
if readIndex < len(byteArray) {
t.Errorf("Byte buffer was only partially read. Buffer length is:%d, readIndex is:%d", len(byteArray), readIndex)
}
if index < len(expected) {
t.Errorf("All expected strings were not compared. expected arr length:%d, matched count:%d", len(expected), index-1)
}
}
byteArray1 := []byte("\n Line 1 \n\n\n L ine4 \nLine 5 \n \n")
expected1 := []string{"", "Line 1", "", "", "L ine4", "Line 5", ""}
testFn(byteArray1, expected1)
byteArray1 = []byte("")
expected1 = []string{}
testFn(byteArray1, expected1)
byteArray1 = []byte("\n\n")
expected1 = []string{"", ""}
testFn(byteArray1, expected1)
}

View File

@ -1,26 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["fake.go"],
importpath = "k8s.io/kubernetes/pkg/util/iptables/testing",
deps = ["//pkg/util/iptables:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,128 +0,0 @@
/*
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 testing
import (
"bytes"
"fmt"
"strings"
"k8s.io/kubernetes/pkg/util/iptables"
)
const (
Destination = "-d "
Source = "-s "
DPort = "--dport "
Protocol = "-p "
Jump = "-j "
Reject = "REJECT"
ToDest = "--to-destination "
Recent = "recent "
MatchSet = "--match-set "
)
type Rule map[string]string
// no-op implementation of iptables Interface
type FakeIPTables struct {
Lines []byte
}
func NewFake() *FakeIPTables {
return &FakeIPTables{}
}
func (*FakeIPTables) GetVersion() (string, error) {
return "0.0.0", nil
}
func (*FakeIPTables) EnsureChain(table iptables.Table, chain iptables.Chain) (bool, error) {
return true, nil
}
func (*FakeIPTables) FlushChain(table iptables.Table, chain iptables.Chain) error {
return nil
}
func (*FakeIPTables) DeleteChain(table iptables.Table, chain iptables.Chain) error {
return nil
}
func (*FakeIPTables) EnsureRule(position iptables.RulePosition, table iptables.Table, chain iptables.Chain, args ...string) (bool, error) {
return true, nil
}
func (*FakeIPTables) DeleteRule(table iptables.Table, chain iptables.Chain, args ...string) error {
return nil
}
func (*FakeIPTables) IsIpv6() bool {
return false
}
func (f *FakeIPTables) Save(table iptables.Table) ([]byte, error) {
lines := make([]byte, len(f.Lines))
copy(lines, f.Lines)
return lines, nil
}
func (f *FakeIPTables) SaveInto(table iptables.Table, buffer *bytes.Buffer) error {
buffer.Write(f.Lines)
return nil
}
func (*FakeIPTables) Restore(table iptables.Table, data []byte, flush iptables.FlushFlag, counters iptables.RestoreCountersFlag) error {
return nil
}
func (f *FakeIPTables) RestoreAll(data []byte, flush iptables.FlushFlag, counters iptables.RestoreCountersFlag) error {
f.Lines = data
return nil
}
func (*FakeIPTables) AddReloadFunc(reloadFunc func()) {}
func (*FakeIPTables) Destroy() {}
func getToken(line, separator string) string {
tokens := strings.Split(line, separator)
if len(tokens) == 2 {
return strings.Split(tokens[1], " ")[0]
}
return ""
}
// GetChain returns a list of rules for the given chain.
// The chain name must match exactly.
// The matching is pretty dumb, don't rely on it for anything but testing.
func (f *FakeIPTables) GetRules(chainName string) (rules []Rule) {
for _, l := range strings.Split(string(f.Lines), "\n") {
if strings.Contains(l, fmt.Sprintf("-A %v", chainName)) {
newRule := Rule(map[string]string{})
for _, arg := range []string{Destination, Source, DPort, Protocol, Jump, ToDest, Recent, MatchSet} {
tok := getToken(l, arg)
if tok != "" {
newRule[arg] = tok
}
}
rules = append(rules, newRule)
}
}
return
}
var _ = iptables.Interface(&FakeIPTables{})

View File

@ -1,64 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"ipvs_linux_test.go",
"ipvs_test.go",
"kernelcheck_linux_test.go",
],
embed = [":go_default_library"],
deps = select({
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/github.com/docker/libnetwork/ipvs:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
"//conditions:default": [],
}),
)
go_library(
name = "go_default_library",
srcs = [
"ipvs.go",
"ipvs_linux.go",
"ipvs_unsupported.go",
"kernelcheck_linux.go",
"kernelcheck_unsupported.go",
],
importpath = "k8s.io/kubernetes/pkg/util/ipvs",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux": [
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/github.com/docker/libnetwork/ipvs:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/util/ipvs/testing:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -1,7 +0,0 @@
reviewers:
- thockin
- m1093782566
approvers:
- thockin
- m1093782566

View File

@ -1,150 +0,0 @@
/*
Copyright 2017 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 ipvs
import (
"net"
"strconv"
"strings"
"fmt"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/utils/exec"
)
// Interface is an injectable interface for running ipvs commands. Implementations must be goroutine-safe.
type Interface interface {
// Flush clears all virtual servers in system. return occurred error immediately.
Flush() error
// AddVirtualServer creates the specified virtual server.
AddVirtualServer(*VirtualServer) error
// UpdateVirtualServer updates an already existing virtual server. If the virtual server does not exist, return error.
UpdateVirtualServer(*VirtualServer) error
// DeleteVirtualServer deletes the specified virtual server. If the virtual server does not exist, return error.
DeleteVirtualServer(*VirtualServer) error
// Given a partial virtual server, GetVirtualServer will return the specified virtual server information in the system.
GetVirtualServer(*VirtualServer) (*VirtualServer, error)
// GetVirtualServers lists all virtual servers in the system.
GetVirtualServers() ([]*VirtualServer, error)
// AddRealServer creates the specified real server for the specified virtual server.
AddRealServer(*VirtualServer, *RealServer) error
// GetRealServers returns all real servers for the specified virtual server.
GetRealServers(*VirtualServer) ([]*RealServer, error)
// DeleteRealServer deletes the specified real server from the specified virtual server.
DeleteRealServer(*VirtualServer, *RealServer) error
// UpdateRealServer updates the specified real server from the specified virtual server.
UpdateRealServer(*VirtualServer, *RealServer) error
}
// VirtualServer is an user-oriented definition of an IPVS virtual server in its entirety.
type VirtualServer struct {
Address net.IP
Protocol string
Port uint16
Scheduler string
Flags ServiceFlags
Timeout uint32
}
// ServiceFlags is used to specify session affinity, ip hash etc.
type ServiceFlags uint32
const (
// FlagPersistent specify IPVS service session affinity
FlagPersistent = 0x1
// FlagHashed specify IPVS service hash flag
FlagHashed = 0x2
// IPVSProxyMode is match set up cluster with ipvs proxy model
IPVSProxyMode = "ipvs"
)
// IPVS required kernel modules.
const (
// ModIPVS is the kernel module "ip_vs"
ModIPVS string = "ip_vs"
// ModIPVSRR is the kernel module "ip_vs_rr"
ModIPVSRR string = "ip_vs_rr"
// ModIPVSWRR is the kernel module "ip_vs_wrr"
ModIPVSWRR string = "ip_vs_wrr"
// ModIPVSSH is the kernel module "ip_vs_sh"
ModIPVSSH string = "ip_vs_sh"
// ModNfConntrackIPV4 is the module "nf_conntrack_ipv4"
ModNfConntrackIPV4 string = "nf_conntrack_ipv4"
// ModNfConntrack is the kernel module "nf_conntrack"
ModNfConntrack string = "nf_conntrack"
)
// Equal check the equality of virtual server.
// We don't use struct == since it doesn't work because of slice.
func (svc *VirtualServer) Equal(other *VirtualServer) bool {
return svc.Address.Equal(other.Address) &&
svc.Protocol == other.Protocol &&
svc.Port == other.Port &&
svc.Scheduler == other.Scheduler &&
svc.Flags == other.Flags &&
svc.Timeout == other.Timeout
}
func (svc *VirtualServer) String() string {
return net.JoinHostPort(svc.Address.String(), strconv.Itoa(int(svc.Port))) + "/" + svc.Protocol
}
// RealServer is an user-oriented definition of an IPVS real server in its entirety.
type RealServer struct {
Address net.IP
Port uint16
Weight int
ActiveConn int
InactiveConn int
}
func (rs *RealServer) String() string {
return net.JoinHostPort(rs.Address.String(), strconv.Itoa(int(rs.Port)))
}
// Equal check the equality of real server.
// We don't use struct == since it doesn't work because of slice.
func (rs *RealServer) Equal(other *RealServer) bool {
return rs.Address.Equal(other.Address) &&
rs.Port == other.Port
}
// GetKernelVersionAndIPVSMods returns the linux kernel version and the required ipvs modules
func GetKernelVersionAndIPVSMods(Executor exec.Interface) (kernelVersion string, ipvsModules []string, err error) {
kernelVersionFile := "/proc/sys/kernel/osrelease"
out, err := Executor.Command("cut", "-f1", "-d", " ", kernelVersionFile).CombinedOutput()
if err != nil {
return "", nil, fmt.Errorf("error getting os release kernel version: %v(%s)", err, out)
}
kernelVersion = strings.TrimSpace(string(out))
// parse kernel version
ver1, err := version.ParseGeneric(kernelVersion)
if err != nil {
return kernelVersion, nil, fmt.Errorf("error parsing kernel version: %v(%s)", err, kernelVersion)
}
// "nf_conntrack_ipv4" has been removed since v4.19
// see https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
ver2, _ := version.ParseGeneric("4.19")
// get required ipvs modules
if ver1.LessThan(ver2) {
ipvsModules = append(ipvsModules, ModIPVS, ModIPVSRR, ModIPVSWRR, ModIPVSSH, ModNfConntrackIPV4)
} else {
ipvsModules = append(ipvsModules, ModIPVS, ModIPVSRR, ModIPVSWRR, ModIPVSSH, ModNfConntrack)
}
return kernelVersion, ipvsModules, nil
}

View File

@ -1,309 +0,0 @@
// +build linux
/*
Copyright 2017 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 ipvs
import (
"errors"
"fmt"
"net"
"strings"
"sync"
"syscall"
libipvs "github.com/docker/libnetwork/ipvs"
"k8s.io/klog"
utilexec "k8s.io/utils/exec"
)
// runner implements ipvs.Interface.
type runner struct {
exec utilexec.Interface
ipvsHandle *libipvs.Handle
mu sync.Mutex // Protect Netlink calls
}
// Protocol is the IPVS service protocol type
type Protocol uint16
// New returns a new Interface which will call ipvs APIs.
func New(exec utilexec.Interface) Interface {
handle, err := libipvs.New("")
if err != nil {
klog.Errorf("IPVS interface can't be initialized, error: %v", err)
return nil
}
return &runner{
exec: exec,
ipvsHandle: handle,
}
}
// AddVirtualServer is part of ipvs.Interface.
func (runner *runner) AddVirtualServer(vs *VirtualServer) error {
svc, err := toIPVSService(vs)
if err != nil {
return err
}
runner.mu.Lock()
defer runner.mu.Unlock()
return runner.ipvsHandle.NewService(svc)
}
// UpdateVirtualServer is part of ipvs.Interface.
func (runner *runner) UpdateVirtualServer(vs *VirtualServer) error {
svc, err := toIPVSService(vs)
if err != nil {
return err
}
runner.mu.Lock()
defer runner.mu.Unlock()
return runner.ipvsHandle.UpdateService(svc)
}
// DeleteVirtualServer is part of ipvs.Interface.
func (runner *runner) DeleteVirtualServer(vs *VirtualServer) error {
svc, err := toIPVSService(vs)
if err != nil {
return err
}
runner.mu.Lock()
defer runner.mu.Unlock()
return runner.ipvsHandle.DelService(svc)
}
// GetVirtualServer is part of ipvs.Interface.
func (runner *runner) GetVirtualServer(vs *VirtualServer) (*VirtualServer, error) {
svc, err := toIPVSService(vs)
if err != nil {
return nil, err
}
runner.mu.Lock()
ipvsSvc, err := runner.ipvsHandle.GetService(svc)
runner.mu.Unlock()
if err != nil {
return nil, err
}
vServ, err := toVirtualServer(ipvsSvc)
if err != nil {
return nil, err
}
return vServ, nil
}
// GetVirtualServers is part of ipvs.Interface.
func (runner *runner) GetVirtualServers() ([]*VirtualServer, error) {
runner.mu.Lock()
ipvsSvcs, err := runner.ipvsHandle.GetServices()
runner.mu.Unlock()
if err != nil {
return nil, err
}
vss := make([]*VirtualServer, 0)
for _, ipvsSvc := range ipvsSvcs {
vs, err := toVirtualServer(ipvsSvc)
if err != nil {
return nil, err
}
vss = append(vss, vs)
}
return vss, nil
}
// Flush is part of ipvs.Interface. Currently we delete IPVS services one by one
func (runner *runner) Flush() error {
runner.mu.Lock()
defer runner.mu.Unlock()
return runner.ipvsHandle.Flush()
}
// AddRealServer is part of ipvs.Interface.
func (runner *runner) AddRealServer(vs *VirtualServer, rs *RealServer) error {
svc, err := toIPVSService(vs)
if err != nil {
return err
}
dst, err := toIPVSDestination(rs)
if err != nil {
return err
}
runner.mu.Lock()
defer runner.mu.Unlock()
return runner.ipvsHandle.NewDestination(svc, dst)
}
// DeleteRealServer is part of ipvs.Interface.
func (runner *runner) DeleteRealServer(vs *VirtualServer, rs *RealServer) error {
svc, err := toIPVSService(vs)
if err != nil {
return err
}
dst, err := toIPVSDestination(rs)
if err != nil {
return err
}
runner.mu.Lock()
defer runner.mu.Unlock()
return runner.ipvsHandle.DelDestination(svc, dst)
}
func (runner *runner) UpdateRealServer(vs *VirtualServer, rs *RealServer) error {
svc, err := toIPVSService(vs)
if err != nil {
return err
}
dst, err := toIPVSDestination(rs)
if err != nil {
return err
}
runner.mu.Lock()
defer runner.mu.Unlock()
return runner.ipvsHandle.UpdateDestination(svc, dst)
}
// GetRealServers is part of ipvs.Interface.
func (runner *runner) GetRealServers(vs *VirtualServer) ([]*RealServer, error) {
svc, err := toIPVSService(vs)
if err != nil {
return nil, err
}
runner.mu.Lock()
dsts, err := runner.ipvsHandle.GetDestinations(svc)
runner.mu.Unlock()
if err != nil {
return nil, err
}
rss := make([]*RealServer, 0)
for _, dst := range dsts {
dst, err := toRealServer(dst)
// TODO: aggregate errors?
if err != nil {
return nil, err
}
rss = append(rss, dst)
}
return rss, nil
}
// toVirtualServer converts an IPVS Service to the equivalent VirtualServer structure.
func toVirtualServer(svc *libipvs.Service) (*VirtualServer, error) {
if svc == nil {
return nil, errors.New("ipvs svc should not be empty")
}
vs := &VirtualServer{
Address: svc.Address,
Port: svc.Port,
Scheduler: svc.SchedName,
Protocol: protocolToString(Protocol(svc.Protocol)),
Timeout: svc.Timeout,
}
// Test Flags >= 0x2, valid Flags ranges [0x2, 0x3]
if svc.Flags&FlagHashed == 0 {
return nil, fmt.Errorf("Flags of successfully created IPVS service should be >= %d since every service is hashed into the service table", FlagHashed)
}
// Sub Flags to 0x2
// 011 -> 001, 010 -> 000
vs.Flags = ServiceFlags(svc.Flags &^ uint32(FlagHashed))
if vs.Address == nil {
if svc.AddressFamily == syscall.AF_INET {
vs.Address = net.IPv4zero
} else {
vs.Address = net.IPv6zero
}
}
return vs, nil
}
// toRealServer converts an IPVS Destination to the equivalent RealServer structure.
func toRealServer(dst *libipvs.Destination) (*RealServer, error) {
if dst == nil {
return nil, errors.New("ipvs destination should not be empty")
}
return &RealServer{
Address: dst.Address,
Port: dst.Port,
Weight: dst.Weight,
ActiveConn: dst.ActiveConnections,
InactiveConn: dst.InactiveConnections,
}, nil
}
// toIPVSService converts a VirtualServer to the equivalent IPVS Service structure.
func toIPVSService(vs *VirtualServer) (*libipvs.Service, error) {
if vs == nil {
return nil, errors.New("virtual server should not be empty")
}
ipvsSvc := &libipvs.Service{
Address: vs.Address,
Protocol: stringToProtocol(vs.Protocol),
Port: vs.Port,
SchedName: vs.Scheduler,
Flags: uint32(vs.Flags),
Timeout: vs.Timeout,
}
if ip4 := vs.Address.To4(); ip4 != nil {
ipvsSvc.AddressFamily = syscall.AF_INET
ipvsSvc.Netmask = 0xffffffff
} else {
ipvsSvc.AddressFamily = syscall.AF_INET6
ipvsSvc.Netmask = 128
}
return ipvsSvc, nil
}
// toIPVSDestination converts a RealServer to the equivalent IPVS Destination structure.
func toIPVSDestination(rs *RealServer) (*libipvs.Destination, error) {
if rs == nil {
return nil, errors.New("real server should not be empty")
}
return &libipvs.Destination{
Address: rs.Address,
Port: rs.Port,
Weight: rs.Weight,
}, nil
}
// stringToProtocolType returns the protocol type for the given name
func stringToProtocol(protocol string) uint16 {
switch strings.ToLower(protocol) {
case "tcp":
return uint16(syscall.IPPROTO_TCP)
case "udp":
return uint16(syscall.IPPROTO_UDP)
case "sctp":
return uint16(syscall.IPPROTO_SCTP)
}
return uint16(0)
}
// protocolTypeToString returns the name for the given protocol.
func protocolToString(proto Protocol) string {
switch proto {
case syscall.IPPROTO_TCP:
return "TCP"
case syscall.IPPROTO_UDP:
return "UDP"
case syscall.IPPROTO_SCTP:
return "SCTP"
}
return ""
}

View File

@ -1,414 +0,0 @@
// +build linux
/*
Copyright 2017 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 ipvs
import (
"fmt"
"net"
"reflect"
"syscall"
"testing"
libipvs "github.com/docker/libnetwork/ipvs"
)
func Test_toVirtualServer(t *testing.T) {
Tests := []struct {
ipvsService libipvs.Service
virtualServer VirtualServer
expectError bool
reason string
}{
{
libipvs.Service{
Flags: 0x0,
},
VirtualServer{},
true,
fmt.Sprintf("IPVS Service Flags should be >= %d, got 0x0", FlagHashed),
},
{
libipvs.Service{
Flags: 0x1,
},
VirtualServer{},
true,
fmt.Sprintf("IPVS Service Flags should be >= %d, got 0x1", FlagHashed),
},
{
libipvs.Service{
Protocol: syscall.IPPROTO_TCP,
Port: 80,
FWMark: 0,
SchedName: "",
Flags: uint32(FlagPersistent + FlagHashed),
Timeout: 0,
Netmask: 0xffffffff,
AddressFamily: syscall.AF_INET,
Address: nil,
PEName: "",
},
VirtualServer{
Address: net.ParseIP("0.0.0.0"),
Protocol: "TCP",
Port: 80,
Scheduler: "",
Flags: ServiceFlags(FlagPersistent),
Timeout: 0,
},
false,
"",
},
{
libipvs.Service{
Protocol: syscall.IPPROTO_UDP,
Port: 33434,
FWMark: 0,
SchedName: "wlc",
Flags: uint32(0 + FlagHashed),
Timeout: 100,
Netmask: 128,
AddressFamily: syscall.AF_INET6,
Address: net.ParseIP("2012::beef"),
PEName: "",
},
VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "UDP",
Port: 33434,
Scheduler: "wlc",
Flags: ServiceFlags(0),
Timeout: 100,
},
false,
"",
},
{
libipvs.Service{
Protocol: 0,
Port: 0,
FWMark: 0,
SchedName: "lc",
Flags: uint32(0 + FlagHashed),
Timeout: 0,
Netmask: 0xffffffff,
AddressFamily: syscall.AF_INET,
Address: net.ParseIP("1.2.3.4"),
PEName: "",
},
VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "",
Port: 0,
Scheduler: "lc",
Flags: ServiceFlags(0),
Timeout: 0,
},
false,
"",
},
{
libipvs.Service{
Protocol: 0,
Port: 0,
FWMark: 0,
SchedName: "wrr",
Flags: uint32(FlagPersistent + FlagHashed),
Timeout: 0,
Netmask: 128,
AddressFamily: syscall.AF_INET6,
Address: nil,
PEName: "",
},
VirtualServer{
Address: net.ParseIP("::0"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: ServiceFlags(FlagPersistent),
Timeout: 0,
},
false,
"",
},
{
libipvs.Service{
Protocol: syscall.IPPROTO_SCTP,
Port: 80,
FWMark: 0,
SchedName: "",
Flags: uint32(FlagPersistent + FlagHashed),
Timeout: 0,
Netmask: 0xffffffff,
AddressFamily: syscall.AF_INET,
Address: nil,
PEName: "",
},
VirtualServer{
Address: net.ParseIP("0.0.0.0"),
Protocol: "SCTP",
Port: 80,
Scheduler: "",
Flags: ServiceFlags(FlagPersistent),
Timeout: 0,
},
false,
"",
},
}
for i := range Tests {
got, err := toVirtualServer(&Tests[i].ipvsService)
if Tests[i].expectError && err == nil {
t.Errorf("case: %d, expected error: %s, got nil", i, Tests[i].reason)
}
if !Tests[i].expectError && err != nil {
t.Errorf("case: %d, unexpected error: %v", i, err)
}
if got != nil && &Tests[i].virtualServer != nil {
if !reflect.DeepEqual(*got, Tests[i].virtualServer) {
t.Errorf("case: %d, got %#v, want %#v", i, *got, Tests[i].virtualServer)
}
}
}
}
func Test_toIPVSService(t *testing.T) {
Tests := []struct {
ipvsService libipvs.Service
virtualServer VirtualServer
}{
{
libipvs.Service{
Protocol: syscall.IPPROTO_TCP,
Port: 80,
FWMark: 0,
SchedName: "",
Flags: 0,
Timeout: 0,
Netmask: 0xffffffff,
AddressFamily: syscall.AF_INET,
Address: net.ParseIP("0.0.0.0"),
PEName: "",
},
VirtualServer{
Address: net.ParseIP("0.0.0.0"),
Protocol: "TCP",
Port: 80,
Scheduler: "",
Flags: 0,
Timeout: 0,
},
},
{
libipvs.Service{
Protocol: syscall.IPPROTO_UDP,
Port: 33434,
FWMark: 0,
SchedName: "wlc",
Flags: 1234,
Timeout: 100,
Netmask: 128,
AddressFamily: syscall.AF_INET6,
Address: net.ParseIP("2012::beef"),
PEName: "",
},
VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "UDP",
Port: 33434,
Scheduler: "wlc",
Flags: 1234,
Timeout: 100,
},
},
{
libipvs.Service{
Protocol: 0,
Port: 0,
FWMark: 0,
SchedName: "lc",
Flags: 0,
Timeout: 0,
Netmask: 0xffffffff,
AddressFamily: syscall.AF_INET,
Address: net.ParseIP("1.2.3.4"),
PEName: "",
},
VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "",
Port: 0,
Scheduler: "lc",
Flags: 0,
Timeout: 0,
},
},
{
libipvs.Service{
Protocol: 0,
Port: 0,
FWMark: 0,
SchedName: "wrr",
Flags: 0,
Timeout: 0,
Netmask: 128,
AddressFamily: syscall.AF_INET6,
Address: net.ParseIP("::0"),
PEName: "",
},
VirtualServer{
Address: net.ParseIP("::0"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
},
}
for i := range Tests {
got, err := toIPVSService(&Tests[i].virtualServer)
if err != nil {
t.Errorf("case: %d, unexpected error: %v", i, err)
}
if !reflect.DeepEqual(*got, Tests[i].ipvsService) {
t.Errorf("case: %d - got %#v, want %#v", i, *got, Tests[i].ipvsService)
}
}
}
func Test_toRealServer(t *testing.T) {
Tests := []struct {
ipvsDestination libipvs.Destination
realServer RealServer
}{
{
libipvs.Destination{
Port: 54321,
ConnectionFlags: 0,
Weight: 1,
Address: net.ParseIP("1.2.3.4"),
},
RealServer{
Address: net.ParseIP("1.2.3.4"),
Port: 54321,
Weight: 1,
},
},
{
libipvs.Destination{
Port: 53,
ConnectionFlags: 0,
Weight: 1,
Address: net.ParseIP("2002::cafe"),
},
RealServer{
Address: net.ParseIP("2002::cafe"),
Port: 53,
Weight: 1,
},
},
}
for i := range Tests {
got, err := toRealServer(&Tests[i].ipvsDestination)
if err != nil {
t.Errorf("case %d unexpected error: %v", i, err)
}
if !reflect.DeepEqual(*got, Tests[i].realServer) {
t.Errorf("case %d Failed to translate Destination - got %#v, want %#v", i, *got, Tests[i].realServer)
}
}
}
func Test_toIPVSDestination(t *testing.T) {
Tests := []struct {
realServer RealServer
ipvsDestination libipvs.Destination
}{
{
RealServer{
Address: net.ParseIP("1.2.3.4"),
Port: 54321,
Weight: 1,
},
libipvs.Destination{
Port: 54321,
ConnectionFlags: 0,
Weight: 1,
Address: net.ParseIP("1.2.3.4"),
},
},
{
RealServer{
Address: net.ParseIP("2002::cafe"),
Port: 53,
Weight: 1,
},
libipvs.Destination{
Port: 53,
ConnectionFlags: 0,
Weight: 1,
Address: net.ParseIP("2002::cafe"),
},
},
}
for i := range Tests {
got, err := toIPVSDestination(&Tests[i].realServer)
if err != nil {
t.Errorf("case %d unexpected error: %v", i, err)
}
if !reflect.DeepEqual(*got, Tests[i].ipvsDestination) {
t.Errorf("case %d failed to translate Destination - got %#v, want %#v", i, *got, Tests[i].ipvsDestination)
}
}
}
func Test_stringToProtocol(t *testing.T) {
tests := []string{
"TCP", "UDP", "ICMP", "SCTP",
}
expected := []uint16{
uint16(syscall.IPPROTO_TCP), uint16(syscall.IPPROTO_UDP), uint16(0), uint16(syscall.IPPROTO_SCTP),
}
for i := range tests {
got := stringToProtocol(tests[i])
if got != expected[i] {
t.Errorf("stringToProtocol() failed - got %#v, want %#v",
got, expected[i])
}
}
}
func Test_protocolToString(t *testing.T) {
tests := []Protocol{
syscall.IPPROTO_TCP, syscall.IPPROTO_UDP, Protocol(0), syscall.IPPROTO_SCTP,
}
expected := []string{
"TCP", "UDP", "", "SCTP",
}
for i := range tests {
got := protocolToString(tests[i])
if got != expected[i] {
t.Errorf("protocolToString() failed - got %#v, want %#v",
got, expected[i])
}
}
}

View File

@ -1,382 +0,0 @@
/*
Copyright 2017 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 ipvs
import (
"net"
"testing"
)
func TestVirtualServerEqual(t *testing.T) {
Tests := []struct {
svcA *VirtualServer
svcB *VirtualServer
equal bool
reason string
}{
{
svcA: &VirtualServer{
Address: net.ParseIP("10.20.30.40"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("10.20.30.41"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
equal: false,
reason: "IPv4 address not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("2017::beef"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
equal: false,
reason: "IPv6 address not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "TCP",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("2012::beeef"),
Protocol: "UDP",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
equal: false,
reason: "Protocol not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "TCP",
Port: 80,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "TCP",
Port: 8080,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
equal: false,
reason: "Port not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "TCP",
Port: 80,
Scheduler: "rr",
Flags: 0,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "TCP",
Port: 80,
Scheduler: "wlc",
Flags: 0,
Timeout: 0,
},
equal: false,
reason: "Scheduler not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "TCP",
Port: 80,
Scheduler: "rr",
Flags: 2,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "TCP",
Port: 80,
Scheduler: "rr",
Flags: 3,
Timeout: 0,
},
equal: false,
reason: "Flags not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 10800,
},
equal: false,
reason: "Timeout not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "TCP",
Port: 80,
Scheduler: "rr",
Flags: 0x1,
Timeout: 10800,
},
svcB: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "TCP",
Port: 80,
Scheduler: "rr",
Flags: 0x1,
Timeout: 10800,
},
equal: true,
reason: "All fields equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "TCP",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
svcB: &VirtualServer{
Address: net.ParseIP("2012::beeef"),
Protocol: "SCTP",
Port: 0,
Scheduler: "wrr",
Flags: 0,
Timeout: 0,
},
equal: false,
reason: "Protocol not equal",
},
{
svcA: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "SCTP",
Port: 80,
Scheduler: "rr",
Flags: 0x1,
Timeout: 10800,
},
svcB: &VirtualServer{
Address: net.ParseIP("1.2.3.4"),
Protocol: "SCTP",
Port: 80,
Scheduler: "rr",
Flags: 0x1,
Timeout: 10800,
},
equal: true,
reason: "All fields equal",
},
}
for i := range Tests {
equal := Tests[i].svcA.Equal(Tests[i].svcB)
if equal != Tests[i].equal {
t.Errorf("case: %d got %v, expected %v, reason: %s", i, equal, Tests[i].equal, Tests[i].reason)
}
}
}
func TestRealServerEqual(t *testing.T) {
Tests := []struct {
rsA *RealServer
rsB *RealServer
equal bool
reason string
}{
{
rsA: &RealServer{
Address: net.ParseIP("10.20.30.40"),
Port: 80,
},
rsB: &RealServer{
Address: net.ParseIP("10.20.30.41"),
Port: 80,
},
equal: false,
reason: "IPv4 address not equal",
},
{
rsA: &RealServer{
Address: net.ParseIP("2012::beef"),
Port: 80,
},
rsB: &RealServer{
Address: net.ParseIP("2017::beef"),
Port: 80,
},
equal: false,
reason: "IPv6 address not equal",
},
{
rsA: &RealServer{
Address: net.ParseIP("2012::beef"),
Port: 80,
},
rsB: &RealServer{
Address: net.ParseIP("2012::beef"),
Port: 8080,
},
equal: false,
reason: "Port not equal",
},
{
rsA: &RealServer{
Address: net.ParseIP("1.2.3.4"),
Port: 3080,
},
rsB: &RealServer{
Address: net.ParseIP("1.2.3.4"),
Port: 3080,
},
equal: true,
reason: "All fields equal",
},
{
rsA: &RealServer{
Address: net.ParseIP("2012::beef"),
Port: 3080,
},
rsB: &RealServer{
Address: net.ParseIP("2012::beef"),
Port: 3080,
},
equal: true,
reason: "All fields equal",
},
}
for i := range Tests {
equal := Tests[i].rsA.Equal(Tests[i].rsB)
if equal != Tests[i].equal {
t.Errorf("case: %d got %v, expected %v, reason: %s", i, equal, Tests[i].equal, Tests[i].reason)
}
}
}
func TestFrontendServiceString(t *testing.T) {
Tests := []struct {
svc *VirtualServer
expected string
}{
{
svc: &VirtualServer{
Address: net.ParseIP("10.20.30.40"),
Protocol: "TCP",
Port: 80,
},
expected: "10.20.30.40:80/TCP",
},
{
svc: &VirtualServer{
Address: net.ParseIP("2012::beef"),
Protocol: "UDP",
Port: 8080,
},
expected: "[2012::beef]:8080/UDP",
},
{
svc: &VirtualServer{
Address: net.ParseIP("10.20.30.41"),
Protocol: "ESP",
Port: 1234,
},
expected: "10.20.30.41:1234/ESP",
},
}
for i := range Tests {
if Tests[i].expected != Tests[i].svc.String() {
t.Errorf("case: %d got %v, expected %v", i, Tests[i].svc.String(), Tests[i].expected)
}
}
}
func TestFrontendDestinationString(t *testing.T) {
Tests := []struct {
svc *RealServer
expected string
}{
{
svc: &RealServer{
Address: net.ParseIP("10.20.30.40"),
Port: 80,
},
expected: "10.20.30.40:80",
},
{
svc: &RealServer{
Address: net.ParseIP("2012::beef"),
Port: 8080,
},
expected: "[2012::beef]:8080",
},
}
for i := range Tests {
if Tests[i].expected != Tests[i].svc.String() {
t.Errorf("case: %d got %v, expected %v", i, Tests[i].svc.String(), Tests[i].expected)
}
}
}

View File

@ -1,75 +0,0 @@
// +build !linux
/*
Copyright 2017 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 ipvs
import (
"fmt"
utilexec "k8s.io/utils/exec"
)
// New returns a dummy Interface for unsupported platform.
func New(utilexec.Interface) Interface {
return &runner{}
}
type runner struct {
}
func (runner *runner) Flush() error {
return fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) AddVirtualServer(*VirtualServer) error {
return fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) UpdateVirtualServer(*VirtualServer) error {
return fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) DeleteVirtualServer(*VirtualServer) error {
return fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) GetVirtualServer(*VirtualServer) (*VirtualServer, error) {
return nil, fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) GetVirtualServers() ([]*VirtualServer, error) {
return nil, fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) AddRealServer(*VirtualServer, *RealServer) error {
return fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) GetRealServers(*VirtualServer) ([]*RealServer, error) {
return nil, fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) DeleteRealServer(*VirtualServer, *RealServer) error {
return fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) UpdateRealServer(*VirtualServer, *RealServer) error {
return fmt.Errorf("IPVS not supported for this platform")
}
var _ = Interface(&runner{})

View File

@ -1,91 +0,0 @@
// +build linux
/*
Copyright 2018 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 ipvs
import (
"fmt"
"regexp"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
utilsexec "k8s.io/utils/exec"
"k8s.io/klog"
)
// RequiredIPVSKernelModulesAvailableCheck tests IPVS required kernel modules.
type RequiredIPVSKernelModulesAvailableCheck struct {
Executor utilsexec.Interface
}
// Name returns label for RequiredIPVSKernelModulesAvailableCheck
func (r RequiredIPVSKernelModulesAvailableCheck) Name() string {
return "RequiredIPVSKernelModulesAvailable"
}
// Check try to validates IPVS required kernel modules exists or not.
// The name of function can not be changed.
func (r RequiredIPVSKernelModulesAvailableCheck) Check() (warnings, errors []error) {
klog.V(1).Infoln("validating the kernel module IPVS required exists in machine or not")
kernelVersion, ipvsModules, err := GetKernelVersionAndIPVSMods(r.Executor)
if err != nil {
errors = append(errors, err)
}
// Find out loaded kernel modules
out, err := r.Executor.Command("cut", "-f1", "-d", " ", "/proc/modules").CombinedOutput()
if err != nil {
errors = append(errors, fmt.Errorf("error getting installed ipvs required kernel modules: %v(%s)", err, out))
return nil, errors
}
mods := strings.Split(string(out), "\n")
wantModules := sets.NewString()
loadModules := sets.NewString()
wantModules.Insert(ipvsModules...)
loadModules.Insert(mods...)
modules := wantModules.Difference(loadModules).UnsortedList()
// Check builtin modules exist or not
if len(modules) != 0 {
builtinModsFilePath := fmt.Sprintf("/lib/modules/%s/modules.builtin", kernelVersion)
out, err := r.Executor.Command("cut", "-f1", "-d", " ", builtinModsFilePath).CombinedOutput()
if err != nil {
errors = append(errors, fmt.Errorf("error getting required builtin kernel modules: %v(%s)", err, out))
return nil, errors
}
builtInModules := sets.NewString()
for _, builtInMode := range ipvsModules {
match, _ := regexp.Match(builtInMode+".ko", out)
if !match {
builtInModules.Insert(string(builtInMode))
}
}
if len(builtInModules) != 0 {
warnings = append(warnings, fmt.Errorf(
"the IPVS proxier will not be used, because the following required kernel modules are not loaded: %v or no builtin kernel ipvs support: %v\n"+
"you can solve this problem with following methods:\n 1. Run 'modprobe -- ' to load missing kernel modules;\n"+
"2. Provide the missing builtin kernel ipvs support\n", modules, builtInModules))
}
}
return warnings, errors
}

View File

@ -1,130 +0,0 @@
/*
Copyright 2018 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 ipvs
import (
"testing"
utilsexec "k8s.io/utils/exec"
fakeexec "k8s.io/utils/exec/testing"
)
func TestRequiredIPVSKernelModulesAvailableCheck(t *testing.T) {
cases := []struct {
caseName string
loadedKernel string
kernelVersion string
builtinKernel string
expectErrors bool
expectWarnings bool
}{
{
caseName: "no installed kernel modules and no builtin kernel modules",
loadedKernel: "",
kernelVersion: "3.13.0-24-generic",
builtinKernel: "",
expectErrors: false,
expectWarnings: true,
},
{
caseName: "no installed kernel modules and missing builtin kernel modules",
loadedKernel: "",
kernelVersion: "3.13.0-24-generic",
builtinKernel: "kernel/net/netfilter/ipvs/ip_vs.ko\n" +
"kernel/net/ipv4/netfilter/nf_conntrack_ipv4.ko",
expectErrors: false,
expectWarnings: true,
},
{
caseName: "no installed kernel modules and own all builtin kernel modules",
loadedKernel: "",
kernelVersion: "3.13.0-24-generic",
builtinKernel: "kernel/net/netfilter/ipvs/ip_vs.ko\n" +
"kernel/net/netfilter/ipvs/ip_vs_rr.ko\n" +
"kernel/net/netfilter/ipvs/ip_vs_wrr.ko\n" +
"kernel/net/netfilter/ipvs/ip_vs_sh.ko\n" +
"kernel/net/ipv4/netfilter/nf_conntrack_ipv4.ko",
expectErrors: false,
expectWarnings: false,
},
{
caseName: "missing installed kernel modules and no builtin kernel modules",
loadedKernel: "ip_vs",
kernelVersion: "3.13.0-24-generic",
builtinKernel: "",
expectErrors: false,
expectWarnings: true,
},
{
caseName: "own all installed kernel modules and no builtin kernel modules",
loadedKernel: "ip_vs\n" + "ip_vs_wrr\n" + "nf_conntrack_ipv4\n" +
"ip_vs_rr\n" + "ip_vs_sh",
kernelVersion: "3.13.0-24-generic",
builtinKernel: "",
expectErrors: false,
expectWarnings: false,
},
{
caseName: "own all installed kernel modules and all builtin kernel modules",
loadedKernel: "ip_vs\n" + "ip_vs_wrr\n" + "nf_conntrack_ipv4\n" + "ip_vs_rr\n" + "ip_vs_sh",
kernelVersion: "3.13.0-24-generic",
builtinKernel: "kernel/net/netfilter/ipvs/ip_vs.ko\n" +
"kernel/net/netfilter/ipvs/ip_vs_rr.ko\n" +
"kernel/net/netfilter/ipvs/ip_vs_wrr.ko\n" +
"kernel/net/netfilter/ipvs/ip_vs_sh.ko\n" +
"kernel/net/ipv4/netfilter/nf_conntrack_ipv4.ko",
expectErrors: false,
expectWarnings: false,
},
}
for i, tc := range cases {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte(cases[i].kernelVersion), nil },
func() ([]byte, error) { return []byte(cases[i].loadedKernel), nil },
func() ([]byte, error) { return []byte(cases[i].builtinKernel), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) utilsexec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) utilsexec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) utilsexec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
check := RequiredIPVSKernelModulesAvailableCheck{
Executor: &fexec,
}
warnings, errors := check.Check()
switch {
case warnings != nil && !tc.expectWarnings:
t.Errorf("RequiredIPVSKernelModulesAvailableCheck: unexpected warnings for installed kernel modules %v and builtin kernel modules %v. Warnings: %v", tc.loadedKernel, tc.builtinKernel, warnings)
case warnings == nil && tc.expectWarnings:
t.Errorf("RequiredIPVSKernelModulesAvailableCheck: expected warnings for installed kernel modules %v and builtin kernel modules %v but got nothing", tc.loadedKernel, tc.builtinKernel)
case errors != nil && !tc.expectErrors:
t.Errorf("RequiredIPVSKernelModulesAvailableCheck: unexpected errors for installed kernel modules %v and builtin kernel modules %v. errors: %v", tc.loadedKernel, tc.builtinKernel, errors)
case errors == nil && tc.expectErrors:
t.Errorf("RequiredIPVSKernelModulesAvailableCheck: expected errors for installed kernel modules %v and builtin kernel modules %v but got nothing", tc.loadedKernel, tc.builtinKernel)
}
}
}

Some files were not shown because too many files have changed in this diff Show More