vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

44
vendor/k8s.io/apimachinery/pkg/util/cache/BUILD generated vendored Normal file
View File

@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"cache_test.go",
"lruexpirecache_test.go",
],
importpath = "k8s.io/apimachinery/pkg/util/cache",
library = ":go_default_library",
deps = [
"//vendor/github.com/golang/groupcache/lru:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"cache.go",
"lruexpirecache.go",
],
importpath = "k8s.io/apimachinery/pkg/util/cache",
deps = ["//vendor/github.com/hashicorp/golang-lru:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

83
vendor/k8s.io/apimachinery/pkg/util/cache/cache.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
/*
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 cache
import (
"sync"
)
const (
shardsCount int = 32
)
type Cache []*cacheShard
func NewCache(maxSize int) Cache {
if maxSize < shardsCount {
maxSize = shardsCount
}
cache := make(Cache, shardsCount)
for i := 0; i < shardsCount; i++ {
cache[i] = &cacheShard{
items: make(map[uint64]interface{}),
maxSize: maxSize / shardsCount,
}
}
return cache
}
func (c Cache) getShard(index uint64) *cacheShard {
return c[index%uint64(shardsCount)]
}
// Returns true if object already existed, false otherwise.
func (c *Cache) Add(index uint64, obj interface{}) bool {
return c.getShard(index).add(index, obj)
}
func (c *Cache) Get(index uint64) (obj interface{}, found bool) {
return c.getShard(index).get(index)
}
type cacheShard struct {
items map[uint64]interface{}
sync.RWMutex
maxSize int
}
// Returns true if object already existed, false otherwise.
func (s *cacheShard) add(index uint64, obj interface{}) bool {
s.Lock()
defer s.Unlock()
_, isOverwrite := s.items[index]
if !isOverwrite && len(s.items) >= s.maxSize {
var randomKey uint64
for randomKey = range s.items {
break
}
delete(s.items, randomKey)
}
s.items[index] = obj
return isOverwrite
}
func (s *cacheShard) get(index uint64) (obj interface{}, found bool) {
s.RLock()
defer s.RUnlock()
obj, found = s.items[index]
return
}

View File

@ -0,0 +1,90 @@
/*
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 cache
import (
"testing"
)
const (
maxTestCacheSize int = shardsCount * 2
)
func ExpectEntry(t *testing.T, cache Cache, index uint64, expectedValue interface{}) bool {
elem, found := cache.Get(index)
if !found {
t.Errorf("Expected to find entry with key %d", index)
return false
} else if elem != expectedValue {
t.Errorf("Expected to find %v, got %v", expectedValue, elem)
return false
}
return true
}
func TestBasic(t *testing.T) {
cache := NewCache(maxTestCacheSize)
cache.Add(1, "xxx")
ExpectEntry(t, cache, 1, "xxx")
}
func TestOverflow(t *testing.T) {
cache := NewCache(maxTestCacheSize)
for i := 0; i < maxTestCacheSize+1; i++ {
cache.Add(uint64(i), "xxx")
}
foundIndexes := make([]uint64, 0)
for i := 0; i < maxTestCacheSize+1; i++ {
_, found := cache.Get(uint64(i))
if found {
foundIndexes = append(foundIndexes, uint64(i))
}
}
if len(foundIndexes) != maxTestCacheSize {
t.Errorf("Expect to find %d elements, got %d %v", maxTestCacheSize, len(foundIndexes), foundIndexes)
}
}
func TestOverwrite(t *testing.T) {
cache := NewCache(maxTestCacheSize)
cache.Add(1, "xxx")
ExpectEntry(t, cache, 1, "xxx")
cache.Add(1, "yyy")
ExpectEntry(t, cache, 1, "yyy")
}
// TestEvict this test will fail sporatically depending on what add()
// selects for the randomKey to be evicted. Ensure that randomKey
// is never the key we most recently added. Since the chance of failure
// on each evict is 50%, if we do it 7 times, it should catch the problem
// if it exists >99% of the time.
func TestEvict(t *testing.T) {
cache := NewCache(shardsCount)
var found bool
for retry := 0; retry < 7; retry++ {
cache.Add(uint64(shardsCount), "xxx")
found = ExpectEntry(t, cache, uint64(shardsCount), "xxx")
if !found {
break
}
cache.Add(0, "xxx")
found = ExpectEntry(t, cache, 0, "xxx")
if !found {
break
}
}
}

View File

@ -0,0 +1,102 @@
/*
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 cache
import (
"sync"
"time"
"github.com/hashicorp/golang-lru"
)
// Clock defines an interface for obtaining the current time
type Clock interface {
Now() time.Time
}
// realClock implements the Clock interface by calling time.Now()
type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
// LRUExpireCache is a cache that ensures the mostly recently accessed keys are returned with
// a ttl beyond which keys are forcibly expired.
type LRUExpireCache struct {
// clock is used to obtain the current time
clock Clock
cache *lru.Cache
lock sync.Mutex
}
// NewLRUExpireCache creates an expiring cache with the given size
func NewLRUExpireCache(maxSize int) *LRUExpireCache {
return NewLRUExpireCacheWithClock(maxSize, realClock{})
}
// NewLRUExpireCacheWithClock creates an expiring cache with the given size, using the specified clock to obtain the current time.
func NewLRUExpireCacheWithClock(maxSize int, clock Clock) *LRUExpireCache {
cache, err := lru.New(maxSize)
if err != nil {
// if called with an invalid size
panic(err)
}
return &LRUExpireCache{clock: clock, cache: cache}
}
type cacheEntry struct {
value interface{}
expireTime time.Time
}
// Add adds the value to the cache at key with the specified maximum duration.
func (c *LRUExpireCache) Add(key interface{}, value interface{}, ttl time.Duration) {
c.lock.Lock()
defer c.lock.Unlock()
c.cache.Add(key, &cacheEntry{value, c.clock.Now().Add(ttl)})
}
// Get returns the value at the specified key from the cache if it exists and is not
// expired, or returns false.
func (c *LRUExpireCache) Get(key interface{}) (interface{}, bool) {
c.lock.Lock()
defer c.lock.Unlock()
e, ok := c.cache.Get(key)
if !ok {
return nil, false
}
if c.clock.Now().After(e.(*cacheEntry).expireTime) {
c.cache.Remove(key)
return nil, false
}
return e.(*cacheEntry).value, true
}
// Remove removes the specified key from the cache if it exists
func (c *LRUExpireCache) Remove(key interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
c.cache.Remove(key)
}
// Keys returns all the keys in the cache, even if they are expired. Subsequent calls to
// get may return not found. It returns all keys from oldest to newest.
func (c *LRUExpireCache) Keys() []interface{} {
c.lock.Lock()
defer c.lock.Unlock()
return c.cache.Keys()
}

View File

@ -0,0 +1,68 @@
/*
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 cache
import (
"testing"
"time"
"k8s.io/apimachinery/pkg/util/clock"
"github.com/golang/groupcache/lru"
)
func expectEntry(t *testing.T, c *LRUExpireCache, key lru.Key, value interface{}) {
result, ok := c.Get(key)
if !ok || result != value {
t.Errorf("Expected cache[%v]: %v, got %v", key, value, result)
}
}
func expectNotEntry(t *testing.T, c *LRUExpireCache, key lru.Key) {
if result, ok := c.Get(key); ok {
t.Errorf("Expected cache[%v] to be empty, got %v", key, result)
}
}
func TestSimpleGet(t *testing.T) {
c := NewLRUExpireCache(10)
c.Add("long-lived", "12345", 10*time.Hour)
expectEntry(t, c, "long-lived", "12345")
}
func TestExpiredGet(t *testing.T) {
fakeClock := clock.NewFakeClock(time.Now())
c := NewLRUExpireCacheWithClock(10, fakeClock)
c.Add("short-lived", "12345", 1*time.Millisecond)
// ensure the entry expired
fakeClock.Step(2 * time.Millisecond)
expectNotEntry(t, c, "short-lived")
}
func TestLRUOverflow(t *testing.T) {
c := NewLRUExpireCache(4)
c.Add("elem1", "1", 10*time.Hour)
c.Add("elem2", "2", 10*time.Hour)
c.Add("elem3", "3", 10*time.Hour)
c.Add("elem4", "4", 10*time.Hour)
c.Add("elem5", "5", 10*time.Hour)
expectNotEntry(t, c, "elem1")
expectEntry(t, c, "elem2", "2")
expectEntry(t, c, "elem3", "3")
expectEntry(t, c, "elem4", "4")
expectEntry(t, c, "elem5", "5")
}

33
vendor/k8s.io/apimachinery/pkg/util/clock/BUILD generated vendored Normal file
View File

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

327
vendor/k8s.io/apimachinery/pkg/util/clock/clock.go generated vendored Normal file
View File

@ -0,0 +1,327 @@
/*
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 clock
import (
"sync"
"time"
)
// Clock allows for injecting fake or real clocks into code that
// needs to do arbitrary things based on time.
type Clock interface {
Now() time.Time
Since(time.Time) time.Duration
After(d time.Duration) <-chan time.Time
NewTimer(d time.Duration) Timer
Sleep(d time.Duration)
Tick(d time.Duration) <-chan time.Time
}
var (
_ = Clock(RealClock{})
_ = Clock(&FakeClock{})
_ = Clock(&IntervalClock{})
)
// RealClock really calls time.Now()
type RealClock struct{}
// Now returns the current time.
func (RealClock) Now() time.Time {
return time.Now()
}
// Since returns time since the specified timestamp.
func (RealClock) Since(ts time.Time) time.Duration {
return time.Since(ts)
}
// Same as time.After(d).
func (RealClock) After(d time.Duration) <-chan time.Time {
return time.After(d)
}
func (RealClock) NewTimer(d time.Duration) Timer {
return &realTimer{
timer: time.NewTimer(d),
}
}
func (RealClock) Tick(d time.Duration) <-chan time.Time {
return time.Tick(d)
}
func (RealClock) Sleep(d time.Duration) {
time.Sleep(d)
}
// FakeClock implements Clock, but returns an arbitrary time.
type FakeClock struct {
lock sync.RWMutex
time time.Time
// waiters are waiting for the fake time to pass their specified time
waiters []fakeClockWaiter
}
type fakeClockWaiter struct {
targetTime time.Time
stepInterval time.Duration
skipIfBlocked bool
destChan chan time.Time
fired bool
}
func NewFakeClock(t time.Time) *FakeClock {
return &FakeClock{
time: t,
}
}
// Now returns f's time.
func (f *FakeClock) Now() time.Time {
f.lock.RLock()
defer f.lock.RUnlock()
return f.time
}
// Since returns time since the time in f.
func (f *FakeClock) Since(ts time.Time) time.Duration {
f.lock.RLock()
defer f.lock.RUnlock()
return f.time.Sub(ts)
}
// Fake version of time.After(d).
func (f *FakeClock) After(d time.Duration) <-chan time.Time {
f.lock.Lock()
defer f.lock.Unlock()
stopTime := f.time.Add(d)
ch := make(chan time.Time, 1) // Don't block!
f.waiters = append(f.waiters, fakeClockWaiter{
targetTime: stopTime,
destChan: ch,
})
return ch
}
// Fake version of time.NewTimer(d).
func (f *FakeClock) NewTimer(d time.Duration) Timer {
f.lock.Lock()
defer f.lock.Unlock()
stopTime := f.time.Add(d)
ch := make(chan time.Time, 1) // Don't block!
timer := &fakeTimer{
fakeClock: f,
waiter: fakeClockWaiter{
targetTime: stopTime,
destChan: ch,
},
}
f.waiters = append(f.waiters, timer.waiter)
return timer
}
func (f *FakeClock) Tick(d time.Duration) <-chan time.Time {
f.lock.Lock()
defer f.lock.Unlock()
tickTime := f.time.Add(d)
ch := make(chan time.Time, 1) // hold one tick
f.waiters = append(f.waiters, fakeClockWaiter{
targetTime: tickTime,
stepInterval: d,
skipIfBlocked: true,
destChan: ch,
})
return ch
}
// Move clock by Duration, notify anyone that's called After, Tick, or NewTimer
func (f *FakeClock) Step(d time.Duration) {
f.lock.Lock()
defer f.lock.Unlock()
f.setTimeLocked(f.time.Add(d))
}
// Sets the time.
func (f *FakeClock) SetTime(t time.Time) {
f.lock.Lock()
defer f.lock.Unlock()
f.setTimeLocked(t)
}
// Actually changes the time and checks any waiters. f must be write-locked.
func (f *FakeClock) setTimeLocked(t time.Time) {
f.time = t
newWaiters := make([]fakeClockWaiter, 0, len(f.waiters))
for i := range f.waiters {
w := &f.waiters[i]
if !w.targetTime.After(t) {
if w.skipIfBlocked {
select {
case w.destChan <- t:
w.fired = true
default:
}
} else {
w.destChan <- t
w.fired = true
}
if w.stepInterval > 0 {
for !w.targetTime.After(t) {
w.targetTime = w.targetTime.Add(w.stepInterval)
}
newWaiters = append(newWaiters, *w)
}
} else {
newWaiters = append(newWaiters, f.waiters[i])
}
}
f.waiters = newWaiters
}
// Returns true if After has been called on f but not yet satisfied (so you can
// write race-free tests).
func (f *FakeClock) HasWaiters() bool {
f.lock.RLock()
defer f.lock.RUnlock()
return len(f.waiters) > 0
}
func (f *FakeClock) Sleep(d time.Duration) {
f.Step(d)
}
// IntervalClock implements Clock, but each invocation of Now steps the clock forward the specified duration
type IntervalClock struct {
Time time.Time
Duration time.Duration
}
// Now returns i's time.
func (i *IntervalClock) Now() time.Time {
i.Time = i.Time.Add(i.Duration)
return i.Time
}
// Since returns time since the time in i.
func (i *IntervalClock) Since(ts time.Time) time.Duration {
return i.Time.Sub(ts)
}
// Unimplemented, will panic.
// TODO: make interval clock use FakeClock so this can be implemented.
func (*IntervalClock) After(d time.Duration) <-chan time.Time {
panic("IntervalClock doesn't implement After")
}
// Unimplemented, will panic.
// TODO: make interval clock use FakeClock so this can be implemented.
func (*IntervalClock) NewTimer(d time.Duration) Timer {
panic("IntervalClock doesn't implement NewTimer")
}
// Unimplemented, will panic.
// TODO: make interval clock use FakeClock so this can be implemented.
func (*IntervalClock) Tick(d time.Duration) <-chan time.Time {
panic("IntervalClock doesn't implement Tick")
}
func (*IntervalClock) Sleep(d time.Duration) {
panic("IntervalClock doesn't implement Sleep")
}
// Timer allows for injecting fake or real timers into code that
// needs to do arbitrary things based on time.
type Timer interface {
C() <-chan time.Time
Stop() bool
Reset(d time.Duration) bool
}
var (
_ = Timer(&realTimer{})
_ = Timer(&fakeTimer{})
)
// realTimer is backed by an actual time.Timer.
type realTimer struct {
timer *time.Timer
}
// C returns the underlying timer's channel.
func (r *realTimer) C() <-chan time.Time {
return r.timer.C
}
// Stop calls Stop() on the underlying timer.
func (r *realTimer) Stop() bool {
return r.timer.Stop()
}
// Reset calls Reset() on the underlying timer.
func (r *realTimer) Reset(d time.Duration) bool {
return r.timer.Reset(d)
}
// fakeTimer implements Timer based on a FakeClock.
type fakeTimer struct {
fakeClock *FakeClock
waiter fakeClockWaiter
}
// C returns the channel that notifies when this timer has fired.
func (f *fakeTimer) C() <-chan time.Time {
return f.waiter.destChan
}
// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise.
func (f *fakeTimer) Stop() bool {
f.fakeClock.lock.Lock()
defer f.fakeClock.lock.Unlock()
newWaiters := make([]fakeClockWaiter, 0, len(f.fakeClock.waiters))
for i := range f.fakeClock.waiters {
w := &f.fakeClock.waiters[i]
if w != &f.waiter {
newWaiters = append(newWaiters, *w)
}
}
f.fakeClock.waiters = newWaiters
return !f.waiter.fired
}
// Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet
// fired, or false otherwise.
func (f *fakeTimer) Reset(d time.Duration) bool {
f.fakeClock.lock.Lock()
defer f.fakeClock.lock.Unlock()
active := !f.waiter.fired
f.waiter.fired = false
f.waiter.targetTime = f.fakeClock.time.Add(d)
return active
}

184
vendor/k8s.io/apimachinery/pkg/util/clock/clock_test.go generated vendored Normal file
View File

@ -0,0 +1,184 @@
/*
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 clock
import (
"testing"
"time"
)
func TestFakeClock(t *testing.T) {
startTime := time.Now()
tc := NewFakeClock(startTime)
tc.Step(time.Second)
now := tc.Now()
if now.Sub(startTime) != time.Second {
t.Errorf("input: %s now=%s gap=%s expected=%s", startTime, now, now.Sub(startTime), time.Second)
}
tt := tc.Now()
tc.SetTime(tt.Add(time.Hour))
if tc.Now().Sub(tt) != time.Hour {
t.Errorf("input: %s now=%s gap=%s expected=%s", tt, tc.Now(), tc.Now().Sub(tt), time.Hour)
}
}
func TestFakeClockSleep(t *testing.T) {
startTime := time.Now()
tc := NewFakeClock(startTime)
tc.Sleep(time.Duration(1) * time.Hour)
now := tc.Now()
if now.Sub(startTime) != time.Hour {
t.Errorf("Fake sleep failed, expected time to advance by one hour, instead, its %v", now.Sub(startTime))
}
}
func TestFakeAfter(t *testing.T) {
tc := NewFakeClock(time.Now())
if tc.HasWaiters() {
t.Errorf("unexpected waiter?")
}
oneSec := tc.After(time.Second)
if !tc.HasWaiters() {
t.Errorf("unexpected lack of waiter?")
}
oneOhOneSec := tc.After(time.Second + time.Millisecond)
twoSec := tc.After(2 * time.Second)
select {
case <-oneSec:
t.Errorf("unexpected channel read")
case <-oneOhOneSec:
t.Errorf("unexpected channel read")
case <-twoSec:
t.Errorf("unexpected channel read")
default:
}
tc.Step(999 * time.Millisecond)
select {
case <-oneSec:
t.Errorf("unexpected channel read")
case <-oneOhOneSec:
t.Errorf("unexpected channel read")
case <-twoSec:
t.Errorf("unexpected channel read")
default:
}
tc.Step(time.Millisecond)
select {
case <-oneSec:
// Expected!
case <-oneOhOneSec:
t.Errorf("unexpected channel read")
case <-twoSec:
t.Errorf("unexpected channel read")
default:
t.Errorf("unexpected non-channel read")
}
tc.Step(time.Millisecond)
select {
case <-oneSec:
// should not double-trigger!
t.Errorf("unexpected channel read")
case <-oneOhOneSec:
// Expected!
case <-twoSec:
t.Errorf("unexpected channel read")
default:
t.Errorf("unexpected non-channel read")
}
}
func TestFakeTick(t *testing.T) {
tc := NewFakeClock(time.Now())
if tc.HasWaiters() {
t.Errorf("unexpected waiter?")
}
oneSec := tc.Tick(time.Second)
if !tc.HasWaiters() {
t.Errorf("unexpected lack of waiter?")
}
oneOhOneSec := tc.Tick(time.Second + time.Millisecond)
twoSec := tc.Tick(2 * time.Second)
select {
case <-oneSec:
t.Errorf("unexpected channel read")
case <-oneOhOneSec:
t.Errorf("unexpected channel read")
case <-twoSec:
t.Errorf("unexpected channel read")
default:
}
tc.Step(999 * time.Millisecond) // t=.999
select {
case <-oneSec:
t.Errorf("unexpected channel read")
case <-oneOhOneSec:
t.Errorf("unexpected channel read")
case <-twoSec:
t.Errorf("unexpected channel read")
default:
}
tc.Step(time.Millisecond) // t=1.000
select {
case <-oneSec:
// Expected!
case <-oneOhOneSec:
t.Errorf("unexpected channel read")
case <-twoSec:
t.Errorf("unexpected channel read")
default:
t.Errorf("unexpected non-channel read")
}
tc.Step(time.Millisecond) // t=1.001
select {
case <-oneSec:
// should not double-trigger!
t.Errorf("unexpected channel read")
case <-oneOhOneSec:
// Expected!
case <-twoSec:
t.Errorf("unexpected channel read")
default:
t.Errorf("unexpected non-channel read")
}
tc.Step(time.Second) // t=2.001
tc.Step(time.Second) // t=3.001
tc.Step(time.Second) // t=4.001
tc.Step(time.Second) // t=5.001
// The one second ticker should not accumulate ticks
accumulatedTicks := 0
drained := false
for !drained {
select {
case <-oneSec:
accumulatedTicks++
default:
drained = true
}
}
if accumulatedTicks != 1 {
t.Errorf("unexpected number of accumulated ticks: %d", accumulatedTicks)
}
}

37
vendor/k8s.io/apimachinery/pkg/util/diff/BUILD generated vendored Normal file
View File

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

273
vendor/k8s.io/apimachinery/pkg/util/diff/diff.go generated vendored Normal file
View File

@ -0,0 +1,273 @@
/*
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 diff
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"text/tabwriter"
"github.com/davecgh/go-spew/spew"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// StringDiff diffs a and b and returns a human readable diff.
func StringDiff(a, b string) string {
ba := []byte(a)
bb := []byte(b)
out := []byte{}
i := 0
for ; i < len(ba) && i < len(bb); i++ {
if ba[i] != bb[i] {
break
}
out = append(out, ba[i])
}
out = append(out, []byte("\n\nA: ")...)
out = append(out, ba[i:]...)
out = append(out, []byte("\n\nB: ")...)
out = append(out, bb[i:]...)
out = append(out, []byte("\n\n")...)
return string(out)
}
// ObjectDiff writes the two objects out as JSON and prints out the identical part of
// the objects followed by the remaining part of 'a' and finally the remaining part of 'b'.
// For debugging tests.
func ObjectDiff(a, b interface{}) string {
ab, err := json.Marshal(a)
if err != nil {
panic(fmt.Sprintf("a: %v", err))
}
bb, err := json.Marshal(b)
if err != nil {
panic(fmt.Sprintf("b: %v", err))
}
return StringDiff(string(ab), string(bb))
}
// ObjectGoPrintDiff is like ObjectDiff, but uses go-spew to print the objects,
// which shows absolutely everything by recursing into every single pointer
// (go's %#v formatters OTOH stop at a certain point). This is needed when you
// can't figure out why reflect.DeepEqual is returning false and nothing is
// showing you differences. This will.
func ObjectGoPrintDiff(a, b interface{}) string {
s := spew.ConfigState{DisableMethods: true}
return StringDiff(
s.Sprintf("%#v", a),
s.Sprintf("%#v", b),
)
}
func ObjectReflectDiff(a, b interface{}) string {
vA, vB := reflect.ValueOf(a), reflect.ValueOf(b)
if vA.Type() != vB.Type() {
return fmt.Sprintf("type A %T and type B %T do not match", a, b)
}
diffs := objectReflectDiff(field.NewPath("object"), vA, vB)
if len(diffs) == 0 {
return "<no diffs>"
}
out := []string{""}
for _, d := range diffs {
out = append(out,
fmt.Sprintf("%s:", d.path),
limit(fmt.Sprintf(" a: %#v", d.a), 80),
limit(fmt.Sprintf(" b: %#v", d.b), 80),
)
}
return strings.Join(out, "\n")
}
func limit(s string, max int) string {
if len(s) > max {
return s[:max]
}
return s
}
func public(s string) bool {
if len(s) == 0 {
return false
}
return s[:1] == strings.ToUpper(s[:1])
}
type diff struct {
path *field.Path
a, b interface{}
}
type orderedDiffs []diff
func (d orderedDiffs) Len() int { return len(d) }
func (d orderedDiffs) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d orderedDiffs) Less(i, j int) bool {
a, b := d[i].path.String(), d[j].path.String()
if a < b {
return true
}
return false
}
func objectReflectDiff(path *field.Path, a, b reflect.Value) []diff {
switch a.Type().Kind() {
case reflect.Struct:
var changes []diff
for i := 0; i < a.Type().NumField(); i++ {
if !public(a.Type().Field(i).Name) {
if reflect.DeepEqual(a.Interface(), b.Interface()) {
continue
}
return []diff{{path: path, a: fmt.Sprintf("%#v", a), b: fmt.Sprintf("%#v", b)}}
}
if sub := objectReflectDiff(path.Child(a.Type().Field(i).Name), a.Field(i), b.Field(i)); len(sub) > 0 {
changes = append(changes, sub...)
}
}
return changes
case reflect.Ptr, reflect.Interface:
if a.IsNil() || b.IsNil() {
switch {
case a.IsNil() && b.IsNil():
return nil
case a.IsNil():
return []diff{{path: path, a: nil, b: b.Interface()}}
default:
return []diff{{path: path, a: a.Interface(), b: nil}}
}
}
return objectReflectDiff(path, a.Elem(), b.Elem())
case reflect.Chan:
if !reflect.DeepEqual(a.Interface(), b.Interface()) {
return []diff{{path: path, a: a.Interface(), b: b.Interface()}}
}
return nil
case reflect.Slice:
lA, lB := a.Len(), b.Len()
l := lA
if lB < lA {
l = lB
}
if lA == lB && lA == 0 {
if a.IsNil() != b.IsNil() {
return []diff{{path: path, a: a.Interface(), b: b.Interface()}}
}
return nil
}
var diffs []diff
for i := 0; i < l; i++ {
if !reflect.DeepEqual(a.Index(i), b.Index(i)) {
diffs = append(diffs, objectReflectDiff(path.Index(i), a.Index(i), b.Index(i))...)
}
}
for i := l; i < lA; i++ {
diffs = append(diffs, diff{path: path.Index(i), a: a.Index(i), b: nil})
}
for i := l; i < lB; i++ {
diffs = append(diffs, diff{path: path.Index(i), a: nil, b: b.Index(i)})
}
return diffs
case reflect.Map:
if reflect.DeepEqual(a.Interface(), b.Interface()) {
return nil
}
aKeys := make(map[interface{}]interface{})
for _, key := range a.MapKeys() {
aKeys[key.Interface()] = a.MapIndex(key).Interface()
}
var missing []diff
for _, key := range b.MapKeys() {
if _, ok := aKeys[key.Interface()]; ok {
delete(aKeys, key.Interface())
if reflect.DeepEqual(a.MapIndex(key).Interface(), b.MapIndex(key).Interface()) {
continue
}
missing = append(missing, objectReflectDiff(path.Key(fmt.Sprintf("%s", key.Interface())), a.MapIndex(key), b.MapIndex(key))...)
continue
}
missing = append(missing, diff{path: path.Key(fmt.Sprintf("%s", key.Interface())), a: nil, b: b.MapIndex(key).Interface()})
}
for key, value := range aKeys {
missing = append(missing, diff{path: path.Key(fmt.Sprintf("%s", key)), a: value, b: nil})
}
if len(missing) == 0 {
missing = append(missing, diff{path: path, a: a.Interface(), b: b.Interface()})
}
sort.Sort(orderedDiffs(missing))
return missing
default:
if reflect.DeepEqual(a.Interface(), b.Interface()) {
return nil
}
if !a.CanInterface() {
return []diff{{path: path, a: fmt.Sprintf("%#v", a), b: fmt.Sprintf("%#v", b)}}
}
return []diff{{path: path, a: a.Interface(), b: b.Interface()}}
}
}
// ObjectGoPrintSideBySide prints a and b as textual dumps side by side,
// enabling easy visual scanning for mismatches.
func ObjectGoPrintSideBySide(a, b interface{}) string {
s := spew.ConfigState{
Indent: " ",
// Extra deep spew.
DisableMethods: true,
}
sA := s.Sdump(a)
sB := s.Sdump(b)
linesA := strings.Split(sA, "\n")
linesB := strings.Split(sB, "\n")
width := 0
for _, s := range linesA {
l := len(s)
if l > width {
width = l
}
}
for _, s := range linesB {
l := len(s)
if l > width {
width = l
}
}
buf := &bytes.Buffer{}
w := tabwriter.NewWriter(buf, width, 0, 1, ' ', 0)
max := len(linesA)
if len(linesB) > max {
max = len(linesB)
}
for i := 0; i < max; i++ {
var a, b string
if i < len(linesA) {
a = linesA[i]
}
if i < len(linesB) {
b = linesB[i]
}
fmt.Fprintf(w, "%s\t%s\n", a, b)
}
w.Flush()
return buf.String()
}

96
vendor/k8s.io/apimachinery/pkg/util/diff/diff_test.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
/*
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 diff
import (
"testing"
)
func TestObjectReflectDiff(t *testing.T) {
type struct1 struct{ A []int }
testCases := map[string]struct {
a, b interface{}
out string
}{
"map": {
a: map[string]int{},
b: map[string]int{},
},
"detect nil map": {
a: map[string]int(nil),
b: map[string]int{},
out: `
object:
a: map[string]int(nil)
b: map[string]int{}`,
},
"detect map changes": {
a: map[string]int{"test": 1, "other": 2},
b: map[string]int{"test": 2, "third": 3},
out: `
object[other]:
a: 2
b: <nil>
object[test]:
a: 1
b: 2
object[third]:
a: <nil>
b: 3`,
},
"nil slice": {a: struct1{A: nil}, b: struct1{A: nil}},
"empty slice": {a: struct1{A: []int{}}, b: struct1{A: []int{}}},
"detect slice changes 1": {a: struct1{A: []int{1}}, b: struct1{A: []int{2}}, out: `
object.A[0]:
a: 1
b: 2`,
},
"detect slice changes 2": {a: struct1{A: []int{}}, b: struct1{A: []int{2}}, out: `
object.A[0]:
a: <nil>
b: 2`,
},
"detect slice changes 3": {a: struct1{A: []int{1}}, b: struct1{A: []int{}}, out: `
object.A[0]:
a: 1
b: <nil>`,
},
"detect nil vs empty slices": {a: struct1{A: nil}, b: struct1{A: []int{}}, out: `
object.A:
a: []int(nil)
b: []int{}`,
},
}
for name, test := range testCases {
expect := test.out
if len(expect) == 0 {
expect = "<no diffs>"
}
if actual := ObjectReflectDiff(test.a, test.b); actual != expect {
t.Errorf("%s: unexpected output: %s", name, actual)
}
}
}
func TestStringDiff(t *testing.T) {
diff := StringDiff("aaabb", "aaacc")
expect := "aaa\n\nA: bb\n\nB: cc\n\n"
if diff != expect {
t.Errorf("diff returned %v", diff)
}
}

36
vendor/k8s.io/apimachinery/pkg/util/errors/BUILD generated vendored Normal file
View File

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

18
vendor/k8s.io/apimachinery/pkg/util/errors/doc.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package errors implements various utility functions and types around errors.
package errors // import "k8s.io/apimachinery/pkg/util/errors"

201
vendor/k8s.io/apimachinery/pkg/util/errors/errors.go generated vendored Normal file
View File

@ -0,0 +1,201 @@
/*
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 errors
import (
"errors"
"fmt"
)
// MessageCountMap contains occurance for each error message.
type MessageCountMap map[string]int
// Aggregate represents an object that contains multiple errors, but does not
// necessarily have singular semantic meaning.
type Aggregate interface {
error
Errors() []error
}
// NewAggregate converts a slice of errors into an Aggregate interface, which
// is itself an implementation of the error interface. If the slice is empty,
// this returns nil.
// It will check if any of the element of input error list is nil, to avoid
// nil pointer panic when call Error().
func NewAggregate(errlist []error) Aggregate {
if len(errlist) == 0 {
return nil
}
// In case of input error list contains nil
var errs []error
for _, e := range errlist {
if e != nil {
errs = append(errs, e)
}
}
if len(errs) == 0 {
return nil
}
return aggregate(errs)
}
// This helper implements the error and Errors interfaces. Keeping it private
// prevents people from making an aggregate of 0 errors, which is not
// an error, but does satisfy the error interface.
type aggregate []error
// Error is part of the error interface.
func (agg aggregate) Error() string {
if len(agg) == 0 {
// This should never happen, really.
return ""
}
if len(agg) == 1 {
return agg[0].Error()
}
result := fmt.Sprintf("[%s", agg[0].Error())
for i := 1; i < len(agg); i++ {
result += fmt.Sprintf(", %s", agg[i].Error())
}
result += "]"
return result
}
// Errors is part of the Aggregate interface.
func (agg aggregate) Errors() []error {
return []error(agg)
}
// Matcher is used to match errors. Returns true if the error matches.
type Matcher func(error) bool
// FilterOut removes all errors that match any of the matchers from the input
// error. If the input is a singular error, only that error is tested. If the
// input implements the Aggregate interface, the list of errors will be
// processed recursively.
//
// This can be used, for example, to remove known-OK errors (such as io.EOF or
// os.PathNotFound) from a list of errors.
func FilterOut(err error, fns ...Matcher) error {
if err == nil {
return nil
}
if agg, ok := err.(Aggregate); ok {
return NewAggregate(filterErrors(agg.Errors(), fns...))
}
if !matchesError(err, fns...) {
return err
}
return nil
}
// matchesError returns true if any Matcher returns true
func matchesError(err error, fns ...Matcher) bool {
for _, fn := range fns {
if fn(err) {
return true
}
}
return false
}
// filterErrors returns any errors (or nested errors, if the list contains
// nested Errors) for which all fns return false. If no errors
// remain a nil list is returned. The resulting silec will have all
// nested slices flattened as a side effect.
func filterErrors(list []error, fns ...Matcher) []error {
result := []error{}
for _, err := range list {
r := FilterOut(err, fns...)
if r != nil {
result = append(result, r)
}
}
return result
}
// Flatten takes an Aggregate, which may hold other Aggregates in arbitrary
// nesting, and flattens them all into a single Aggregate, recursively.
func Flatten(agg Aggregate) Aggregate {
result := []error{}
if agg == nil {
return nil
}
for _, err := range agg.Errors() {
if a, ok := err.(Aggregate); ok {
r := Flatten(a)
if r != nil {
result = append(result, r.Errors()...)
}
} else {
if err != nil {
result = append(result, err)
}
}
}
return NewAggregate(result)
}
// CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate
func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate {
if m == nil {
return nil
}
result := make([]error, 0, len(m))
for errStr, count := range m {
var countStr string
if count > 1 {
countStr = fmt.Sprintf(" (repeated %v times)", count)
}
result = append(result, fmt.Errorf("%v%v", errStr, countStr))
}
return NewAggregate(result)
}
// Reduce will return err or, if err is an Aggregate and only has one item,
// the first item in the aggregate.
func Reduce(err error) error {
if agg, ok := err.(Aggregate); ok && err != nil {
switch len(agg.Errors()) {
case 1:
return agg.Errors()[0]
case 0:
return nil
}
}
return err
}
// AggregateGoroutines runs the provided functions in parallel, stuffing all
// non-nil errors into the returned Aggregate.
// Returns nil if all the functions complete successfully.
func AggregateGoroutines(funcs ...func() error) Aggregate {
errChan := make(chan error, len(funcs))
for _, f := range funcs {
go func(f func() error) { errChan <- f() }(f)
}
errs := make([]error, 0)
for i := 0; i < cap(errChan); i++ {
if err := <-errChan; err != nil {
errs = append(errs, err)
}
}
return NewAggregate(errs)
}
// ErrPreconditionViolated is returned when the precondition is violated
var ErrPreconditionViolated = errors.New("precondition is violated")

View File

@ -0,0 +1,368 @@
/*
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 errors
import (
"fmt"
"reflect"
"sort"
"testing"
)
func TestEmptyAggregate(t *testing.T) {
var slice []error
var agg Aggregate
var err error
agg = NewAggregate(slice)
if agg != nil {
t.Errorf("expected nil, got %#v", agg)
}
err = NewAggregate(slice)
if err != nil {
t.Errorf("expected nil, got %#v", err)
}
// This is not normally possible, but pedantry demands I test it.
agg = aggregate(slice) // empty aggregate
if s := agg.Error(); s != "" {
t.Errorf("expected empty string, got %q", s)
}
if s := agg.Errors(); len(s) != 0 {
t.Errorf("expected empty slice, got %#v", s)
}
err = agg.(error)
if s := err.Error(); s != "" {
t.Errorf("expected empty string, got %q", s)
}
}
func TestAggregateWithNil(t *testing.T) {
var slice []error
slice = []error{nil}
var agg Aggregate
var err error
agg = NewAggregate(slice)
if agg != nil {
t.Errorf("expected nil, got %#v", agg)
}
err = NewAggregate(slice)
if err != nil {
t.Errorf("expected nil, got %#v", err)
}
// Append a non-nil error
slice = append(slice, fmt.Errorf("err"))
agg = NewAggregate(slice)
if agg == nil {
t.Errorf("expected non-nil")
}
if s := agg.Error(); s != "err" {
t.Errorf("expected 'err', got %q", s)
}
if s := agg.Errors(); len(s) != 1 {
t.Errorf("expected one-element slice, got %#v", s)
}
if s := agg.Errors()[0].Error(); s != "err" {
t.Errorf("expected 'err', got %q", s)
}
err = agg.(error)
if err == nil {
t.Errorf("expected non-nil")
}
if s := err.Error(); s != "err" {
t.Errorf("expected 'err', got %q", s)
}
}
func TestSingularAggregate(t *testing.T) {
var slice []error = []error{fmt.Errorf("err")}
var agg Aggregate
var err error
agg = NewAggregate(slice)
if agg == nil {
t.Errorf("expected non-nil")
}
if s := agg.Error(); s != "err" {
t.Errorf("expected 'err', got %q", s)
}
if s := agg.Errors(); len(s) != 1 {
t.Errorf("expected one-element slice, got %#v", s)
}
if s := agg.Errors()[0].Error(); s != "err" {
t.Errorf("expected 'err', got %q", s)
}
err = agg.(error)
if err == nil {
t.Errorf("expected non-nil")
}
if s := err.Error(); s != "err" {
t.Errorf("expected 'err', got %q", s)
}
}
func TestPluralAggregate(t *testing.T) {
var slice []error = []error{fmt.Errorf("abc"), fmt.Errorf("123")}
var agg Aggregate
var err error
agg = NewAggregate(slice)
if agg == nil {
t.Errorf("expected non-nil")
}
if s := agg.Error(); s != "[abc, 123]" {
t.Errorf("expected '[abc, 123]', got %q", s)
}
if s := agg.Errors(); len(s) != 2 {
t.Errorf("expected two-elements slice, got %#v", s)
}
if s := agg.Errors()[0].Error(); s != "abc" {
t.Errorf("expected '[abc, 123]', got %q", s)
}
err = agg.(error)
if err == nil {
t.Errorf("expected non-nil")
}
if s := err.Error(); s != "[abc, 123]" {
t.Errorf("expected '[abc, 123]', got %q", s)
}
}
func TestFilterOut(t *testing.T) {
testCases := []struct {
err error
filter []Matcher
expected error
}{
{
nil,
[]Matcher{},
nil,
},
{
aggregate{},
[]Matcher{},
nil,
},
{
aggregate{fmt.Errorf("abc")},
[]Matcher{},
aggregate{fmt.Errorf("abc")},
},
{
aggregate{fmt.Errorf("abc")},
[]Matcher{func(err error) bool { return false }},
aggregate{fmt.Errorf("abc")},
},
{
aggregate{fmt.Errorf("abc")},
[]Matcher{func(err error) bool { return true }},
nil,
},
{
aggregate{fmt.Errorf("abc")},
[]Matcher{func(err error) bool { return false }, func(err error) bool { return false }},
aggregate{fmt.Errorf("abc")},
},
{
aggregate{fmt.Errorf("abc")},
[]Matcher{func(err error) bool { return false }, func(err error) bool { return true }},
nil,
},
{
aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
[]Matcher{func(err error) bool { return err.Error() == "def" }},
aggregate{fmt.Errorf("abc"), fmt.Errorf("ghi")},
},
{
aggregate{aggregate{fmt.Errorf("abc")}},
[]Matcher{},
aggregate{aggregate{fmt.Errorf("abc")}},
},
{
aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
[]Matcher{},
aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
},
{
aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
[]Matcher{func(err error) bool { return err.Error() == "def" }},
aggregate{aggregate{fmt.Errorf("abc")}},
},
}
for i, testCase := range testCases {
err := FilterOut(testCase.err, testCase.filter...)
if !reflect.DeepEqual(testCase.expected, err) {
t.Errorf("%d: expected %v, got %v", i, testCase.expected, err)
}
}
}
func TestFlatten(t *testing.T) {
testCases := []struct {
agg Aggregate
expected Aggregate
}{
{
nil,
nil,
},
{
aggregate{},
nil,
},
{
aggregate{fmt.Errorf("abc")},
aggregate{fmt.Errorf("abc")},
},
{
aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
},
{
aggregate{aggregate{fmt.Errorf("abc")}},
aggregate{fmt.Errorf("abc")},
},
{
aggregate{aggregate{aggregate{fmt.Errorf("abc")}}},
aggregate{fmt.Errorf("abc")},
},
{
aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
aggregate{fmt.Errorf("abc"), fmt.Errorf("def")},
},
{
aggregate{aggregate{aggregate{fmt.Errorf("abc")}, fmt.Errorf("def"), aggregate{fmt.Errorf("ghi")}}},
aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
},
}
for i, testCase := range testCases {
agg := Flatten(testCase.agg)
if !reflect.DeepEqual(testCase.expected, agg) {
t.Errorf("%d: expected %v, got %v", i, testCase.expected, agg)
}
}
}
func TestCreateAggregateFromMessageCountMap(t *testing.T) {
testCases := []struct {
name string
mcm MessageCountMap
expected Aggregate
}{
{
"input has single instance of one message",
MessageCountMap{"abc": 1},
aggregate{fmt.Errorf("abc")},
},
{
"input has multiple messages",
MessageCountMap{"abc": 2, "ghi": 1},
aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")},
},
{
"input has multiple messages",
MessageCountMap{"ghi": 1, "abc": 2},
aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")},
},
}
var expected, agg []error
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
if testCase.expected != nil {
expected = testCase.expected.Errors()
sort.Slice(expected, func(i, j int) bool { return expected[i].Error() < expected[j].Error() })
}
if testCase.mcm != nil {
agg = CreateAggregateFromMessageCountMap(testCase.mcm).Errors()
sort.Slice(agg, func(i, j int) bool { return agg[i].Error() < agg[j].Error() })
}
if !reflect.DeepEqual(expected, agg) {
t.Errorf("expected %v, got %v", expected, agg)
}
})
}
}
func TestAggregateGoroutines(t *testing.T) {
testCases := []struct {
errs []error
expected map[string]bool // can't compare directly to Aggregate due to non-deterministic ordering
}{
{
[]error{},
nil,
},
{
[]error{nil},
nil,
},
{
[]error{nil, nil},
nil,
},
{
[]error{fmt.Errorf("1")},
map[string]bool{"1": true},
},
{
[]error{fmt.Errorf("1"), nil},
map[string]bool{"1": true},
},
{
[]error{fmt.Errorf("1"), fmt.Errorf("267")},
map[string]bool{"1": true, "267": true},
},
{
[]error{fmt.Errorf("1"), nil, fmt.Errorf("1234")},
map[string]bool{"1": true, "1234": true},
},
{
[]error{nil, fmt.Errorf("1"), nil, fmt.Errorf("1234"), fmt.Errorf("22")},
map[string]bool{"1": true, "1234": true, "22": true},
},
}
for i, testCase := range testCases {
funcs := make([]func() error, len(testCase.errs))
for i := range testCase.errs {
err := testCase.errs[i]
funcs[i] = func() error { return err }
}
agg := AggregateGoroutines(funcs...)
if agg == nil {
if len(testCase.expected) > 0 {
t.Errorf("%d: expected %v, got nil", i, testCase.expected)
}
continue
}
if len(agg.Errors()) != len(testCase.expected) {
t.Errorf("%d: expected %d errors in aggregate, got %v", i, len(testCase.expected), agg)
continue
}
for _, err := range agg.Errors() {
if !testCase.expected[err.Error()] {
t.Errorf("%d: expected %v, got aggregate containing %v", i, testCase.expected, err)
}
}
}
}

33
vendor/k8s.io/apimachinery/pkg/util/framer/BUILD generated vendored Normal file
View File

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

167
vendor/k8s.io/apimachinery/pkg/util/framer/framer.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
/*
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 framer implements simple frame decoding techniques for an io.ReadCloser
package framer
import (
"encoding/binary"
"encoding/json"
"io"
)
type lengthDelimitedFrameWriter struct {
w io.Writer
h [4]byte
}
func NewLengthDelimitedFrameWriter(w io.Writer) io.Writer {
return &lengthDelimitedFrameWriter{w: w}
}
// Write writes a single frame to the nested writer, prepending it with the length in
// in bytes of data (as a 4 byte, bigendian uint32).
func (w *lengthDelimitedFrameWriter) Write(data []byte) (int, error) {
binary.BigEndian.PutUint32(w.h[:], uint32(len(data)))
n, err := w.w.Write(w.h[:])
if err != nil {
return 0, err
}
if n != len(w.h) {
return 0, io.ErrShortWrite
}
return w.w.Write(data)
}
type lengthDelimitedFrameReader struct {
r io.ReadCloser
remaining int
}
// NewLengthDelimitedFrameReader returns an io.Reader that will decode length-prefixed
// frames off of a stream.
//
// The protocol is:
//
// stream: message ...
// message: prefix body
// prefix: 4 byte uint32 in BigEndian order, denotes length of body
// body: bytes (0..prefix)
//
// If the buffer passed to Read is not long enough to contain an entire frame, io.ErrShortRead
// will be returned along with the number of bytes read.
func NewLengthDelimitedFrameReader(r io.ReadCloser) io.ReadCloser {
return &lengthDelimitedFrameReader{r: r}
}
// Read attempts to read an entire frame into data. If that is not possible, io.ErrShortBuffer
// is returned and subsequent calls will attempt to read the last frame. A frame is complete when
// err is nil.
func (r *lengthDelimitedFrameReader) Read(data []byte) (int, error) {
if r.remaining <= 0 {
header := [4]byte{}
n, err := io.ReadAtLeast(r.r, header[:4], 4)
if err != nil {
return 0, err
}
if n != 4 {
return 0, io.ErrUnexpectedEOF
}
frameLength := int(binary.BigEndian.Uint32(header[:]))
r.remaining = frameLength
}
expect := r.remaining
max := expect
if max > len(data) {
max = len(data)
}
n, err := io.ReadAtLeast(r.r, data[:max], int(max))
r.remaining -= n
if err == io.ErrShortBuffer || r.remaining > 0 {
return n, io.ErrShortBuffer
}
if err != nil {
return n, err
}
if n != expect {
return n, io.ErrUnexpectedEOF
}
return n, nil
}
func (r *lengthDelimitedFrameReader) Close() error {
return r.r.Close()
}
type jsonFrameReader struct {
r io.ReadCloser
decoder *json.Decoder
remaining []byte
}
// NewJSONFramedReader returns an io.Reader that will decode individual JSON objects off
// of a wire.
//
// The boundaries between each frame are valid JSON objects. A JSON parsing error will terminate
// the read.
func NewJSONFramedReader(r io.ReadCloser) io.ReadCloser {
return &jsonFrameReader{
r: r,
decoder: json.NewDecoder(r),
}
}
// ReadFrame decodes the next JSON object in the stream, or returns an error. The returned
// byte slice will be modified the next time ReadFrame is invoked and should not be altered.
func (r *jsonFrameReader) Read(data []byte) (int, error) {
// Return whatever remaining data exists from an in progress frame
if n := len(r.remaining); n > 0 {
if n <= len(data) {
data = append(data[0:0], r.remaining...)
r.remaining = nil
return n, nil
}
n = len(data)
data = append(data[0:0], r.remaining[:n]...)
r.remaining = r.remaining[n:]
return n, io.ErrShortBuffer
}
// RawMessage#Unmarshal appends to data - we reset the slice down to 0 and will either see
// data written to data, or be larger than data and a different array.
n := len(data)
m := json.RawMessage(data[:0])
if err := r.decoder.Decode(&m); err != nil {
return 0, err
}
// If capacity of data is less than length of the message, decoder will allocate a new slice
// and set m to it, which means we need to copy the partial result back into data and preserve
// the remaining result for subsequent reads.
if len(m) > n {
data = append(data[0:0], m[:n]...)
r.remaining = m[n:]
return n, io.ErrShortBuffer
}
return len(m), nil
}
func (r *jsonFrameReader) Close() error {
return r.r.Close()
}

View File

@ -0,0 +1,176 @@
/*
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 framer
import (
"bytes"
"io"
"io/ioutil"
"testing"
)
func TestRead(t *testing.T) {
data := []byte{
0x00, 0x00, 0x00, 0x04,
0x01, 0x02, 0x03, 0x04,
0x00, 0x00, 0x00, 0x03,
0x05, 0x06, 0x07,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
0x08,
}
b := bytes.NewBuffer(data)
r := NewLengthDelimitedFrameReader(ioutil.NopCloser(b))
buf := make([]byte, 1)
if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x01}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x02}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read the remaining frame
buf = make([]byte, 2)
if n, err := r.Read(buf); err != nil && n != 2 && bytes.Equal(buf, []byte{0x03, 0x04}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read with buffer equal to frame
buf = make([]byte, 3)
if n, err := r.Read(buf); err != nil && n != 3 && bytes.Equal(buf, []byte{0x05, 0x06, 0x07}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read empty frame
buf = make([]byte, 3)
if n, err := r.Read(buf); err != nil && n != 0 && bytes.Equal(buf, []byte{}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read with larger buffer than frame
buf = make([]byte, 3)
if n, err := r.Read(buf); err != nil && n != 1 && bytes.Equal(buf, []byte{0x08}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read EOF
if n, err := r.Read(buf); err != io.EOF && n != 0 {
t.Fatalf("unexpected: %v %d", err, n)
}
}
func TestReadLarge(t *testing.T) {
data := []byte{
0x00, 0x00, 0x00, 0x04,
0x01, 0x02, 0x03, 0x04,
0x00, 0x00, 0x00, 0x03,
0x05, 0x06, 0x07,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
0x08,
}
b := bytes.NewBuffer(data)
r := NewLengthDelimitedFrameReader(ioutil.NopCloser(b))
buf := make([]byte, 40)
if n, err := r.Read(buf); err != nil && n != 4 && bytes.Equal(buf, []byte{0x01, 0x02, 0x03, 0x04}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
if n, err := r.Read(buf); err != nil && n != 3 && bytes.Equal(buf, []byte{0x05, 0x06, 0x7}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
if n, err := r.Read(buf); err != nil && n != 0 && bytes.Equal(buf, []byte{}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
if n, err := r.Read(buf); err != nil && n != 1 && bytes.Equal(buf, []byte{0x08}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read EOF
if n, err := r.Read(buf); err != io.EOF && n != 0 {
t.Fatalf("unexpected: %v %d", err, n)
}
}
func TestReadInvalidFrame(t *testing.T) {
data := []byte{
0x00, 0x00, 0x00, 0x04,
0x01, 0x02,
}
b := bytes.NewBuffer(data)
r := NewLengthDelimitedFrameReader(ioutil.NopCloser(b))
buf := make([]byte, 1)
if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x01}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read the remaining frame
buf = make([]byte, 3)
if n, err := r.Read(buf); err != io.ErrUnexpectedEOF && n != 1 && bytes.Equal(buf, []byte{0x02}) {
t.Fatalf("unexpected: %v %d %v", err, n, buf)
}
// read EOF
if n, err := r.Read(buf); err != io.EOF && n != 0 {
t.Fatalf("unexpected: %v %d", err, n)
}
}
func TestJSONFrameReader(t *testing.T) {
b := bytes.NewBufferString("{\"test\":true}\n1\n[\"a\"]")
r := NewJSONFramedReader(ioutil.NopCloser(b))
buf := make([]byte, 20)
if n, err := r.Read(buf); err != nil || n != 13 || string(buf[:n]) != `{"test":true}` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `1` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != nil || n != 5 || string(buf[:n]) != `["a"]` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != io.EOF || n != 0 {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
}
func TestJSONFrameReaderShortBuffer(t *testing.T) {
b := bytes.NewBufferString("{\"test\":true}\n1\n[\"a\"]")
r := NewJSONFramedReader(ioutil.NopCloser(b))
buf := make([]byte, 3)
if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `{"t` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `est` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `":t` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `rue` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `}` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `1` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `["a` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != nil || n != 2 || string(buf[:n]) != `"]` {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
if n, err := r.Read(buf); err != io.EOF || n != 0 {
t.Fatalf("unexpected: %v %d %q", err, n, buf)
}
}

39
vendor/k8s.io/apimachinery/pkg/util/httpstream/BUILD generated vendored Normal file
View File

@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["httpstream_test.go"],
importpath = "k8s.io/apimachinery/pkg/util/httpstream",
library = ":go_default_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"httpstream.go",
],
importpath = "k8s.io/apimachinery/pkg/util/httpstream",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy:all-srcs",
],
tags = ["automanaged"],
)

19
vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
/*
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 httpstream adds multiplexed streaming support to HTTP requests and
// responses via connection upgrades.
package httpstream // import "k8s.io/apimachinery/pkg/util/httpstream"

View File

@ -0,0 +1,149 @@
/*
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 httpstream
import (
"fmt"
"io"
"net/http"
"strings"
"time"
)
const (
HeaderConnection = "Connection"
HeaderUpgrade = "Upgrade"
HeaderProtocolVersion = "X-Stream-Protocol-Version"
HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions"
)
// NewStreamHandler defines a function that is called when a new Stream is
// received. If no error is returned, the Stream is accepted; otherwise,
// the stream is rejected. After the reply frame has been sent, replySent is closed.
type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error
// NoOpNewStreamHandler is a stream handler that accepts a new stream and
// performs no other logic.
func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil }
// Dialer knows how to open a streaming connection to a server.
type Dialer interface {
// Dial opens a streaming connection to a server using one of the protocols
// specified (in order of most preferred to least preferred).
Dial(protocols ...string) (Connection, string, error)
}
// UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade
// HTTP requests to support multiplexed bidirectional streams. After RoundTrip()
// is invoked, if the upgrade is successful, clients may retrieve the upgraded
// connection by calling UpgradeRoundTripper.Connection().
type UpgradeRoundTripper interface {
http.RoundTripper
// NewConnection validates the response and creates a new Connection.
NewConnection(resp *http.Response) (Connection, error)
}
// ResponseUpgrader knows how to upgrade HTTP requests and responses to
// add streaming support to them.
type ResponseUpgrader interface {
// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
// streams. newStreamHandler will be called asynchronously whenever the
// other end of the upgraded connection creates a new stream.
UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection
}
// Connection represents an upgraded HTTP connection.
type Connection interface {
// CreateStream creates a new Stream with the supplied headers.
CreateStream(headers http.Header) (Stream, error)
// Close resets all streams and closes the connection.
Close() error
// CloseChan returns a channel that is closed when the underlying connection is closed.
CloseChan() <-chan bool
// SetIdleTimeout sets the amount of time the connection may remain idle before
// it is automatically closed.
SetIdleTimeout(timeout time.Duration)
}
// Stream represents a bidirectional communications channel that is part of an
// upgraded connection.
type Stream interface {
io.ReadWriteCloser
// Reset closes both directions of the stream, indicating that neither client
// or server can use it any more.
Reset() error
// Headers returns the headers used to create the stream.
Headers() http.Header
// Identifier returns the stream's ID.
Identifier() uint32
}
// IsUpgradeRequest returns true if the given request is a connection upgrade request
func IsUpgradeRequest(req *http.Request) bool {
for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] {
if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) {
return true
}
}
return false
}
func negotiateProtocol(clientProtocols, serverProtocols []string) string {
for i := range clientProtocols {
for j := range serverProtocols {
if clientProtocols[i] == serverProtocols[j] {
return clientProtocols[i]
}
}
}
return ""
}
// Handshake performs a subprotocol negotiation. If the client did request a
// subprotocol, Handshake will select the first common value found in
// serverProtocols. If a match is found, Handshake adds a response header
// indicating the chosen subprotocol. If no match is found, HTTP forbidden is
// returned, along with a response header containing the list of protocols the
// server can accept.
func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) {
clientProtocols := req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)]
if len(clientProtocols) == 0 {
// Kube 1.0 clients didn't support subprotocol negotiation.
// TODO require clientProtocols once Kube 1.0 is no longer supported
return "", nil
}
if len(serverProtocols) == 0 {
// Kube 1.0 servers didn't support subprotocol negotiation. This is mainly for testing.
// TODO require serverProtocols once Kube 1.0 is no longer supported
return "", nil
}
negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols)
if len(negotiatedProtocol) == 0 {
w.WriteHeader(http.StatusForbidden)
for i := range serverProtocols {
w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i])
}
fmt.Fprintf(w, "unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols)
return "", fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server supports %v", clientProtocols, serverProtocols)
}
w.Header().Add(HeaderProtocolVersion, negotiatedProtocol)
return negotiatedProtocol, nil
}

View File

@ -0,0 +1,125 @@
/*
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 httpstream
import (
"net/http"
"reflect"
"testing"
)
type responseWriter struct {
header http.Header
statusCode *int
}
func newResponseWriter() *responseWriter {
return &responseWriter{
header: make(http.Header),
}
}
func (r *responseWriter) Header() http.Header {
return r.header
}
func (r *responseWriter) WriteHeader(code int) {
r.statusCode = &code
}
func (r *responseWriter) Write([]byte) (int, error) {
return 0, nil
}
func TestHandshake(t *testing.T) {
tests := map[string]struct {
clientProtocols []string
serverProtocols []string
expectedProtocol string
expectError bool
}{
"no client protocols": {
clientProtocols: []string{},
serverProtocols: []string{"a", "b"},
expectedProtocol: "",
},
"no common protocol": {
clientProtocols: []string{"c"},
serverProtocols: []string{"a", "b"},
expectedProtocol: "",
expectError: true,
},
"common protocol": {
clientProtocols: []string{"b"},
serverProtocols: []string{"a", "b"},
expectedProtocol: "b",
},
}
for name, test := range tests {
req, err := http.NewRequest("GET", "http://www.example.com/", nil)
if err != nil {
t.Fatalf("%s: error creating request: %v", name, err)
}
for _, p := range test.clientProtocols {
req.Header.Add(HeaderProtocolVersion, p)
}
w := newResponseWriter()
negotiated, err := Handshake(req, w, test.serverProtocols)
// verify negotiated protocol
if e, a := test.expectedProtocol, negotiated; e != a {
t.Errorf("%s: protocol: expected %q, got %q", name, e, a)
}
if test.expectError {
if err == nil {
t.Errorf("%s: expected error but did not get one", name)
}
if w.statusCode == nil {
t.Errorf("%s: expected w.statusCode to be set", name)
} else if e, a := http.StatusForbidden, *w.statusCode; e != a {
t.Errorf("%s: w.statusCode: expected %d, got %d", name, e, a)
}
if e, a := test.serverProtocols, w.Header()[HeaderAcceptedProtocolVersions]; !reflect.DeepEqual(e, a) {
t.Errorf("%s: accepted server protocols: expected %v, got %v", name, e, a)
}
continue
}
if !test.expectError && err != nil {
t.Errorf("%s: unexpected error: %v", name, err)
continue
}
if w.statusCode != nil {
t.Errorf("%s: unexpected non-nil w.statusCode: %d", name, w.statusCode)
}
if len(test.expectedProtocol) == 0 {
if len(w.Header()[HeaderProtocolVersion]) > 0 {
t.Errorf("%s: unexpected protocol version response header: %s", name, w.Header()[HeaderProtocolVersion])
}
continue
}
// verify response headers
if e, a := []string{test.expectedProtocol}, w.Header()[HeaderProtocolVersion]; !reflect.DeepEqual(e, a) {
t.Errorf("%s: protocol response header: expected %v, got %v", name, e, a)
}
}
}

View File

@ -0,0 +1,57 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"connection_test.go",
"roundtripper_test.go",
"upgrade_test.go",
],
importpath = "k8s.io/apimachinery/pkg/util/httpstream/spdy",
library = ":go_default_library",
deps = [
"//vendor/github.com/elazarl/goproxy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"connection.go",
"roundtripper.go",
"upgrade.go",
],
importpath = "k8s.io/apimachinery/pkg/util/httpstream/spdy",
deps = [
"//vendor/github.com/docker/spdystream:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/third_party/forked/golang/netutil: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

@ -0,0 +1,145 @@
/*
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 spdy
import (
"net"
"net/http"
"sync"
"time"
"github.com/docker/spdystream"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/util/httpstream"
)
// connection maintains state about a spdystream.Connection and its associated
// streams.
type connection struct {
conn *spdystream.Connection
streams []httpstream.Stream
streamLock sync.Mutex
newStreamHandler httpstream.NewStreamHandler
}
// NewClientConnection creates a new SPDY client connection.
func NewClientConnection(conn net.Conn) (httpstream.Connection, error) {
spdyConn, err := spdystream.NewConnection(conn, false)
if err != nil {
defer conn.Close()
return nil, err
}
return newConnection(spdyConn, httpstream.NoOpNewStreamHandler), nil
}
// NewServerConnection creates a new SPDY server connection. newStreamHandler
// will be invoked when the server receives a newly created stream from the
// client.
func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) {
spdyConn, err := spdystream.NewConnection(conn, true)
if err != nil {
defer conn.Close()
return nil, err
}
return newConnection(spdyConn, newStreamHandler), nil
}
// newConnection returns a new connection wrapping conn. newStreamHandler
// will be invoked when the server receives a newly created stream from the
// client.
func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
c := &connection{conn: conn, newStreamHandler: newStreamHandler}
go conn.Serve(c.newSpdyStream)
return c
}
// createStreamResponseTimeout indicates how long to wait for the other side to
// acknowledge the new stream before timing out.
const createStreamResponseTimeout = 30 * time.Second
// Close first sends a reset for all of the connection's streams, and then
// closes the underlying spdystream.Connection.
func (c *connection) Close() error {
c.streamLock.Lock()
for _, s := range c.streams {
// calling Reset instead of Close ensures that all streams are fully torn down
s.Reset()
}
c.streams = make([]httpstream.Stream, 0)
c.streamLock.Unlock()
// now that all streams are fully torn down, it's safe to call close on the underlying connection,
// which should be able to terminate immediately at this point, instead of waiting for any
// remaining graceful stream termination.
return c.conn.Close()
}
// CreateStream creates a new stream with the specified headers and registers
// it with the connection.
func (c *connection) CreateStream(headers http.Header) (httpstream.Stream, error) {
stream, err := c.conn.CreateStream(headers, nil, false)
if err != nil {
return nil, err
}
if err = stream.WaitTimeout(createStreamResponseTimeout); err != nil {
return nil, err
}
c.registerStream(stream)
return stream, nil
}
// registerStream adds the stream s to the connection's list of streams that
// it owns.
func (c *connection) registerStream(s httpstream.Stream) {
c.streamLock.Lock()
c.streams = append(c.streams, s)
c.streamLock.Unlock()
}
// CloseChan returns a channel that, when closed, indicates that the underlying
// spdystream.Connection has been closed.
func (c *connection) CloseChan() <-chan bool {
return c.conn.CloseChan()
}
// newSpdyStream is the internal new stream handler used by spdystream.Connection.Serve.
// It calls connection's newStreamHandler, giving it the opportunity to accept or reject
// the stream. If newStreamHandler returns an error, the stream is rejected. If not, the
// stream is accepted and registered with the connection.
func (c *connection) newSpdyStream(stream *spdystream.Stream) {
replySent := make(chan struct{})
err := c.newStreamHandler(stream, replySent)
rejectStream := (err != nil)
if rejectStream {
glog.Warningf("Stream rejected: %v", err)
stream.Reset()
return
}
c.registerStream(stream)
stream.SendReply(http.Header{}, rejectStream)
close(replySent)
}
// SetIdleTimeout sets the amount of time the connection may remain idle before
// it is automatically closed.
func (c *connection) SetIdleTimeout(timeout time.Duration) {
c.conn.SetIdleTimeout(timeout)
}

View File

@ -0,0 +1,164 @@
/*
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 spdy
import (
"io"
"net"
"net/http"
"sync"
"testing"
"time"
"k8s.io/apimachinery/pkg/util/httpstream"
)
func runProxy(t *testing.T, backendUrl string, proxyUrl chan<- string, proxyDone chan<- struct{}) {
listener, err := net.Listen("tcp4", "localhost:0")
if err != nil {
t.Fatalf("error listening: %v", err)
}
defer listener.Close()
proxyUrl <- listener.Addr().String()
clientConn, err := listener.Accept()
if err != nil {
t.Errorf("proxy: error accepting client connection: %v", err)
return
}
backendConn, err := net.Dial("tcp4", backendUrl)
if err != nil {
t.Errorf("proxy: error dialing backend: %v", err)
return
}
defer backendConn.Close()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
io.Copy(backendConn, clientConn)
}()
go func() {
defer wg.Done()
io.Copy(clientConn, backendConn)
}()
wg.Wait()
proxyDone <- struct{}{}
}
func runServer(t *testing.T, backendUrl chan<- string, serverDone chan<- struct{}) {
listener, err := net.Listen("tcp4", "localhost:0")
if err != nil {
t.Fatalf("server: error listening: %v", err)
}
defer listener.Close()
backendUrl <- listener.Addr().String()
conn, err := listener.Accept()
if err != nil {
t.Errorf("server: error accepting connection: %v", err)
return
}
streamChan := make(chan httpstream.Stream)
replySentChan := make(chan (<-chan struct{}))
spdyConn, err := NewServerConnection(conn, func(stream httpstream.Stream, replySent <-chan struct{}) error {
streamChan <- stream
replySentChan <- replySent
return nil
})
if err != nil {
t.Errorf("server: error creating spdy connection: %v", err)
return
}
stream := <-streamChan
replySent := <-replySentChan
<-replySent
buf := make([]byte, 1)
_, err = stream.Read(buf)
if err != io.EOF {
t.Errorf("server: unexpected read error: %v", err)
return
}
<-spdyConn.CloseChan()
raw := spdyConn.(*connection).conn
if err := raw.Wait(15 * time.Second); err != nil {
t.Errorf("server: timed out waiting for connection closure: %v", err)
}
serverDone <- struct{}{}
}
func TestConnectionCloseIsImmediateThroughAProxy(t *testing.T) {
serverDone := make(chan struct{})
backendUrlChan := make(chan string)
go runServer(t, backendUrlChan, serverDone)
backendUrl := <-backendUrlChan
proxyDone := make(chan struct{})
proxyUrlChan := make(chan string)
go runProxy(t, backendUrl, proxyUrlChan, proxyDone)
proxyUrl := <-proxyUrlChan
conn, err := net.Dial("tcp4", proxyUrl)
if err != nil {
t.Fatalf("client: error connecting to proxy: %v", err)
}
spdyConn, err := NewClientConnection(conn)
if err != nil {
t.Fatalf("client: error creating spdy connection: %v", err)
}
if _, err := spdyConn.CreateStream(http.Header{}); err != nil {
t.Fatalf("client: error creating stream: %v", err)
}
spdyConn.Close()
raw := spdyConn.(*connection).conn
if err := raw.Wait(15 * time.Second); err != nil {
t.Fatalf("client: timed out waiting for connection closure: %v", err)
}
expired := time.NewTimer(15 * time.Second)
defer expired.Stop()
i := 0
for {
select {
case <-expired.C:
t.Fatalf("timed out waiting for proxy and/or server closure")
case <-serverDone:
i++
case <-proxyDone:
i++
}
if i == 2 {
break
}
}
}

View File

@ -0,0 +1,326 @@
/*
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 spdy
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/httpstream"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/third_party/forked/golang/netutil"
)
// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports
// multiplexed streams. After RoundTrip() is invoked, Conn will be set
// and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface.
type SpdyRoundTripper struct {
//tlsConfig holds the TLS configuration settings to use when connecting
//to the remote server.
tlsConfig *tls.Config
/* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper
must be safe for use by multiple concurrent goroutines. If this is absolutely
necessary, we could keep a map from http.Request to net.Conn. In practice,
a client will create an http.Client, set the transport to a new insteace of
SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue.
*/
// conn is the underlying network connection to the remote server.
conn net.Conn
// Dialer is the dialer used to connect. Used if non-nil.
Dialer *net.Dialer
// proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment
// Used primarily for mocking the proxy discovery in tests.
proxier func(req *http.Request) (*url.URL, error)
// followRedirects indicates if the round tripper should examine responses for redirects and
// follow them.
followRedirects bool
}
var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{}
var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{}
var _ utilnet.Dialer = &SpdyRoundTripper{}
// NewRoundTripper creates a new SpdyRoundTripper that will use
// the specified tlsConfig.
func NewRoundTripper(tlsConfig *tls.Config, followRedirects bool) httpstream.UpgradeRoundTripper {
return NewSpdyRoundTripper(tlsConfig, followRedirects)
}
// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use
// the specified tlsConfig. This function is mostly meant for unit tests.
func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects bool) *SpdyRoundTripper {
return &SpdyRoundTripper{tlsConfig: tlsConfig, followRedirects: followRedirects}
}
// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during
// proxying with a spdy roundtripper.
func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config {
return s.tlsConfig
}
// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer.
func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
conn, err := s.dial(req)
if err != nil {
return nil, err
}
if err := req.Write(conn); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
// dial dials the host specified by req, using TLS if appropriate, optionally
// using a proxy server if one is configured via environment variables.
func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) {
proxier := s.proxier
if proxier == nil {
proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
}
proxyURL, err := proxier(req)
if err != nil {
return nil, err
}
if proxyURL == nil {
return s.dialWithoutProxy(req.URL)
}
// ensure we use a canonical host with proxyReq
targetHost := netutil.CanonicalAddr(req.URL)
// proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support
proxyReq := http.Request{
Method: "CONNECT",
URL: &url.URL{},
Host: targetHost,
}
if pa := s.proxyAuth(proxyURL); pa != "" {
proxyReq.Header = http.Header{}
proxyReq.Header.Set("Proxy-Authorization", pa)
}
proxyDialConn, err := s.dialWithoutProxy(proxyURL)
if err != nil {
return nil, err
}
proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil)
_, err = proxyClientConn.Do(&proxyReq)
if err != nil && err != httputil.ErrPersistEOF {
return nil, err
}
rwc, _ := proxyClientConn.Hijack()
if req.URL.Scheme != "https" {
return rwc, nil
}
host, _, err := net.SplitHostPort(targetHost)
if err != nil {
return nil, err
}
tlsConfig := s.tlsConfig
switch {
case tlsConfig == nil:
tlsConfig = &tls.Config{ServerName: host}
case len(tlsConfig.ServerName) == 0:
tlsConfig = tlsConfig.Clone()
tlsConfig.ServerName = host
}
tlsConn := tls.Client(rwc, tlsConfig)
// need to manually call Handshake() so we can call VerifyHostname() below
if err := tlsConn.Handshake(); err != nil {
return nil, err
}
// Return if we were configured to skip validation
if tlsConfig.InsecureSkipVerify {
return tlsConn, nil
}
if err := tlsConn.VerifyHostname(tlsConfig.ServerName); err != nil {
return nil, err
}
return tlsConn, nil
}
// dialWithoutProxy dials the host specified by url, using TLS if appropriate.
func (s *SpdyRoundTripper) dialWithoutProxy(url *url.URL) (net.Conn, error) {
dialAddr := netutil.CanonicalAddr(url)
if url.Scheme == "http" {
if s.Dialer == nil {
return net.Dial("tcp", dialAddr)
} else {
return s.Dialer.Dial("tcp", dialAddr)
}
}
// TODO validate the TLSClientConfig is set up?
var conn *tls.Conn
var err error
if s.Dialer == nil {
conn, err = tls.Dial("tcp", dialAddr, s.tlsConfig)
} else {
conn, err = tls.DialWithDialer(s.Dialer, "tcp", dialAddr, s.tlsConfig)
}
if err != nil {
return nil, err
}
// Return if we were configured to skip validation
if s.tlsConfig != nil && s.tlsConfig.InsecureSkipVerify {
return conn, nil
}
host, _, err := net.SplitHostPort(dialAddr)
if err != nil {
return nil, err
}
if s.tlsConfig != nil && len(s.tlsConfig.ServerName) > 0 {
host = s.tlsConfig.ServerName
}
err = conn.VerifyHostname(host)
if err != nil {
return nil, err
}
return conn, nil
}
// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header
func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string {
if proxyURL == nil || proxyURL.User == nil {
return ""
}
credentials := proxyURL.User.String()
encodedAuth := base64.StdEncoding.EncodeToString([]byte(credentials))
return fmt.Sprintf("Basic %s", encodedAuth)
}
// RoundTrip executes the Request and upgrades it. After a successful upgrade,
// clients may call SpdyRoundTripper.Connection() to retrieve the upgraded
// connection.
func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
header := utilnet.CloneHeader(req.Header)
header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
header.Add(httpstream.HeaderUpgrade, HeaderSpdy31)
var (
conn net.Conn
rawResponse []byte
err error
)
if s.followRedirects {
conn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, req.URL, header, req.Body, s)
} else {
clone := utilnet.CloneRequest(req)
clone.Header = header
conn, err = s.Dial(clone)
}
if err != nil {
return nil, err
}
responseReader := bufio.NewReader(
io.MultiReader(
bytes.NewBuffer(rawResponse),
conn,
),
)
resp, err := http.ReadResponse(responseReader, nil)
if err != nil {
if conn != nil {
conn.Close()
}
return nil, err
}
s.conn = conn
return resp, nil
}
// NewConnection validates the upgrade response, creating and returning a new
// httpstream.Connection if there were no errors.
func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) {
connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection))
upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade))
if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
defer resp.Body.Close()
responseError := ""
responseErrorBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
responseError = "unable to read error from server response"
} else {
// TODO: I don't belong here, I should be abstracted from this class
if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil {
if status, ok := obj.(*metav1.Status); ok {
return nil, &apierrors.StatusError{ErrStatus: *status}
}
}
responseError = string(responseErrorBytes)
responseError = strings.TrimSpace(responseError)
}
return nil, fmt.Errorf("unable to upgrade connection: %s", responseError)
}
return NewClientConnection(s.conn)
}
// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection
var statusScheme = runtime.NewScheme()
// ParameterCodec knows about query parameters used with the meta v1 API spec.
var statusCodecs = serializer.NewCodecFactory(statusScheme)
func init() {
statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion,
&metav1.Status{},
)
}

View File

@ -0,0 +1,529 @@
/*
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 spdy
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync/atomic"
"testing"
"github.com/elazarl/goproxy"
"k8s.io/apimachinery/pkg/util/httpstream"
)
// be sure to unset environment variable https_proxy (if exported) before testing, otherwise the testing will fail unexpectedly.
func TestRoundTripAndNewConnection(t *testing.T) {
for _, redirect := range []bool{false, true} {
t.Run(fmt.Sprintf("redirect = %t", redirect), func(t *testing.T) {
localhostPool := x509.NewCertPool()
if !localhostPool.AppendCertsFromPEM(localhostCert) {
t.Errorf("error setting up localhostCert pool")
}
httpsServerInvalidHostname := func(h http.Handler) *httptest.Server {
cert, err := tls.X509KeyPair(exampleCert, exampleKey)
if err != nil {
t.Errorf("https (invalid hostname): proxy_test: %v", err)
}
ts := httptest.NewUnstartedServer(h)
ts.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
}
ts.StartTLS()
return ts
}
httpsServerValidHostname := func(h http.Handler) *httptest.Server {
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
t.Errorf("https (valid hostname): proxy_test: %v", err)
}
ts := httptest.NewUnstartedServer(h)
ts.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
}
ts.StartTLS()
return ts
}
testCases := map[string]struct {
serverFunc func(http.Handler) *httptest.Server
proxyServerFunc func(http.Handler) *httptest.Server
proxyAuth *url.Userinfo
clientTLS *tls.Config
serverConnectionHeader string
serverUpgradeHeader string
serverStatusCode int
shouldError bool
}{
"no headers": {
serverFunc: httptest.NewServer,
serverConnectionHeader: "",
serverUpgradeHeader: "",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: true,
},
"no upgrade header": {
serverFunc: httptest.NewServer,
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: true,
},
"no connection header": {
serverFunc: httptest.NewServer,
serverConnectionHeader: "",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: true,
},
"no switching protocol status code": {
serverFunc: httptest.NewServer,
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusForbidden,
shouldError: true,
},
"http": {
serverFunc: httptest.NewServer,
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"https (invalid hostname + InsecureSkipVerify)": {
serverFunc: httpsServerInvalidHostname,
clientTLS: &tls.Config{InsecureSkipVerify: true},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"https (invalid hostname + hostname verification)": {
serverFunc: httpsServerInvalidHostname,
clientTLS: &tls.Config{InsecureSkipVerify: false},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: true,
},
"https (valid hostname + RootCAs)": {
serverFunc: httpsServerValidHostname,
clientTLS: &tls.Config{RootCAs: localhostPool},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"proxied http->http": {
serverFunc: httptest.NewServer,
proxyServerFunc: httptest.NewServer,
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"proxied https (invalid hostname + InsecureSkipVerify) -> http": {
serverFunc: httptest.NewServer,
proxyServerFunc: httpsServerInvalidHostname,
clientTLS: &tls.Config{InsecureSkipVerify: true},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"proxied https with auth (invalid hostname + InsecureSkipVerify) -> http": {
serverFunc: httptest.NewServer,
proxyServerFunc: httpsServerInvalidHostname,
proxyAuth: url.UserPassword("proxyuser", "proxypasswd"),
clientTLS: &tls.Config{InsecureSkipVerify: true},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"proxied https (invalid hostname + hostname verification) -> http": {
serverFunc: httptest.NewServer,
proxyServerFunc: httpsServerInvalidHostname,
clientTLS: &tls.Config{InsecureSkipVerify: false},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: true, // fails because the client doesn't trust the proxy
},
"proxied https (valid hostname + RootCAs) -> http": {
serverFunc: httptest.NewServer,
proxyServerFunc: httpsServerValidHostname,
clientTLS: &tls.Config{RootCAs: localhostPool},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"proxied https with auth (valid hostname + RootCAs) -> http": {
serverFunc: httptest.NewServer,
proxyServerFunc: httpsServerValidHostname,
proxyAuth: url.UserPassword("proxyuser", "proxypasswd"),
clientTLS: &tls.Config{RootCAs: localhostPool},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"proxied https (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": {
serverFunc: httpsServerInvalidHostname,
proxyServerFunc: httpsServerInvalidHostname,
clientTLS: &tls.Config{InsecureSkipVerify: true},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false, // works because the test proxy ignores TLS errors
},
"proxied https with auth (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": {
serverFunc: httpsServerInvalidHostname,
proxyServerFunc: httpsServerInvalidHostname,
proxyAuth: url.UserPassword("proxyuser", "proxypasswd"),
clientTLS: &tls.Config{InsecureSkipVerify: true},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false, // works because the test proxy ignores TLS errors
},
"proxied https (invalid hostname + hostname verification) -> https (invalid hostname)": {
serverFunc: httpsServerInvalidHostname,
proxyServerFunc: httpsServerInvalidHostname,
clientTLS: &tls.Config{InsecureSkipVerify: false},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: true, // fails because the client doesn't trust the proxy
},
"proxied https (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": {
serverFunc: httpsServerValidHostname,
proxyServerFunc: httpsServerValidHostname,
clientTLS: &tls.Config{RootCAs: localhostPool},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
"proxied https with auth (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": {
serverFunc: httpsServerValidHostname,
proxyServerFunc: httpsServerValidHostname,
proxyAuth: url.UserPassword("proxyuser", "proxypasswd"),
clientTLS: &tls.Config{RootCAs: localhostPool},
serverConnectionHeader: "Upgrade",
serverUpgradeHeader: "SPDY/3.1",
serverStatusCode: http.StatusSwitchingProtocols,
shouldError: false,
},
}
for k, testCase := range testCases {
server := testCase.serverFunc(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if testCase.shouldError {
if e, a := httpstream.HeaderUpgrade, req.Header.Get(httpstream.HeaderConnection); e != a {
t.Fatalf("%s: Expected connection=upgrade header, got '%s", k, a)
}
w.Header().Set(httpstream.HeaderConnection, testCase.serverConnectionHeader)
w.Header().Set(httpstream.HeaderUpgrade, testCase.serverUpgradeHeader)
w.WriteHeader(testCase.serverStatusCode)
return
}
streamCh := make(chan httpstream.Stream)
responseUpgrader := NewResponseUpgrader()
spdyConn := responseUpgrader.UpgradeResponse(w, req, func(s httpstream.Stream, replySent <-chan struct{}) error {
streamCh <- s
return nil
})
if spdyConn == nil {
t.Fatalf("%s: unexpected nil spdyConn", k)
}
defer spdyConn.Close()
stream := <-streamCh
io.Copy(stream, stream)
}))
defer server.Close()
serverURL, err := url.Parse(server.URL)
if err != nil {
t.Fatalf("%s: Error creating request: %s", k, err)
}
req, err := http.NewRequest("GET", server.URL, nil)
if err != nil {
t.Fatalf("%s: Error creating request: %s", k, err)
}
spdyTransport := NewSpdyRoundTripper(testCase.clientTLS, redirect)
var proxierCalled bool
var proxyCalledWithHost string
var proxyCalledWithAuth bool
var proxyCalledWithAuthHeader string
if testCase.proxyServerFunc != nil {
proxyHandler := goproxy.NewProxyHttpServer()
proxyHandler.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
proxyCalledWithHost = host
proxyAuthHeaderName := "Proxy-Authorization"
_, proxyCalledWithAuth = ctx.Req.Header[proxyAuthHeaderName]
proxyCalledWithAuthHeader = ctx.Req.Header.Get(proxyAuthHeaderName)
return goproxy.OkConnect, host
})
proxy := testCase.proxyServerFunc(proxyHandler)
spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) {
proxierCalled = true
proxyURL, err := url.Parse(proxy.URL)
if err != nil {
return nil, err
}
proxyURL.User = testCase.proxyAuth
return proxyURL, nil
}
defer proxy.Close()
}
client := &http.Client{Transport: spdyTransport}
resp, err := client.Do(req)
var conn httpstream.Connection
if err == nil {
conn, err = spdyTransport.NewConnection(resp)
}
haveErr := err != nil
if e, a := testCase.shouldError, haveErr; e != a {
t.Fatalf("%s: shouldError=%t, got %t: %v", k, e, a, err)
}
if testCase.shouldError {
continue
}
defer conn.Close()
if resp.StatusCode != http.StatusSwitchingProtocols {
t.Fatalf("%s: expected http 101 switching protocols, got %d", k, resp.StatusCode)
}
stream, err := conn.CreateStream(http.Header{})
if err != nil {
t.Fatalf("%s: error creating client stream: %s", k, err)
}
n, err := stream.Write([]byte("hello"))
if err != nil {
t.Fatalf("%s: error writing to stream: %s", k, err)
}
if n != 5 {
t.Fatalf("%s: Expected to write 5 bytes, but actually wrote %d", k, n)
}
b := make([]byte, 5)
n, err = stream.Read(b)
if err != nil {
t.Fatalf("%s: error reading from stream: %s", k, err)
}
if n != 5 {
t.Fatalf("%s: Expected to read 5 bytes, but actually read %d", k, n)
}
if e, a := "hello", string(b[0:n]); e != a {
t.Fatalf("%s: expected '%s', got '%s'", k, e, a)
}
if testCase.proxyServerFunc != nil {
if !proxierCalled {
t.Fatalf("%s: Expected to use a proxy but proxier in SpdyRoundTripper wasn't called", k)
}
if proxyCalledWithHost != serverURL.Host {
t.Fatalf("%s: Expected to see a call to the proxy for backend %q, got %q", k, serverURL.Host, proxyCalledWithHost)
}
}
var expectedProxyAuth string
if testCase.proxyAuth != nil {
encodedCredentials := base64.StdEncoding.EncodeToString([]byte(testCase.proxyAuth.String()))
expectedProxyAuth = "Basic " + encodedCredentials
}
if len(expectedProxyAuth) == 0 && proxyCalledWithAuth {
t.Fatalf("%s: Proxy authorization unexpected, got %q", k, proxyCalledWithAuthHeader)
}
if proxyCalledWithAuthHeader != expectedProxyAuth {
t.Fatalf("%s: Expected to see a call to the proxy with credentials %q, got %q", k, testCase.proxyAuth, proxyCalledWithAuthHeader)
}
}
})
}
}
func TestRoundTripRedirects(t *testing.T) {
tests := []struct {
redirects int32
expectSuccess bool
}{
{0, true},
{1, true},
{10, true},
{11, false},
}
for _, test := range tests {
t.Run(fmt.Sprintf("with %d redirects", test.redirects), func(t *testing.T) {
var redirects int32 = 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if redirects < test.redirects {
redirects = atomic.AddInt32(&redirects, 1)
http.Redirect(w, req, "redirect", http.StatusFound)
return
}
streamCh := make(chan httpstream.Stream)
responseUpgrader := NewResponseUpgrader()
spdyConn := responseUpgrader.UpgradeResponse(w, req, func(s httpstream.Stream, replySent <-chan struct{}) error {
streamCh <- s
return nil
})
if spdyConn == nil {
t.Fatalf("unexpected nil spdyConn")
}
defer spdyConn.Close()
stream := <-streamCh
io.Copy(stream, stream)
}))
defer server.Close()
req, err := http.NewRequest("GET", server.URL, nil)
if err != nil {
t.Fatalf("Error creating request: %s", err)
}
spdyTransport := NewSpdyRoundTripper(nil, true)
client := &http.Client{Transport: spdyTransport}
resp, err := client.Do(req)
if test.expectSuccess {
if err != nil {
t.Fatalf("error calling Do: %v", err)
}
} else {
if err == nil {
t.Fatalf("expecting an error")
} else if !strings.Contains(err.Error(), "too many redirects") {
t.Fatalf("expecting too many redirects, got %v", err)
}
return
}
conn, err := spdyTransport.NewConnection(resp)
if err != nil {
t.Fatalf("error calling NewConnection: %v", err)
}
defer conn.Close()
if resp.StatusCode != http.StatusSwitchingProtocols {
t.Fatalf("expected http 101 switching protocols, got %d", resp.StatusCode)
}
stream, err := conn.CreateStream(http.Header{})
if err != nil {
t.Fatalf("error creating client stream: %s", err)
}
n, err := stream.Write([]byte("hello"))
if err != nil {
t.Fatalf("error writing to stream: %s", err)
}
if n != 5 {
t.Fatalf("Expected to write 5 bytes, but actually wrote %d", n)
}
b := make([]byte, 5)
n, err = stream.Read(b)
if err != nil {
t.Fatalf("error reading from stream: %s", err)
}
if n != 5 {
t.Fatalf("Expected to read 5 bytes, but actually read %d", n)
}
if e, a := "hello", string(b[0:n]); e != a {
t.Fatalf("expected '%s', got '%s'", e, a)
}
})
}
}
// exampleCert was generated from crypto/tls/generate_cert.go with the following command:
// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var exampleCert = []byte(`-----BEGIN CERTIFICATE-----
MIIBdzCCASGgAwIBAgIRAOVTAdPnfbS5V85mfS90TfIwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
QQCoVSqeu8TBvF+70T7Jm4340YQNhds6IxjRoifenYodAO1dnKGrcbF266DJGunh
nIjQH7B12tduhl0fLK4Ezf7/AgMBAAGjUDBOMA4GA1UdDwEB/wQEAwICpDATBgNV
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4
YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EAk1kVa5uZ/AzwYDVcS9bpM/czwjjV
xq3VeSCfmNa2uNjbFvodmCRwZOHUvipAMGCUCV6j5vMrJ8eMj8tCQ36W9A==
-----END CERTIFICATE-----`)
var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAKhVKp67xMG8X7vRPsmbjfjRhA2F2zojGNGiJ96dih0A7V2coatx
sXbroMka6eGciNAfsHXa126GXR8srgTN/v8CAwEAAQJASdzdD7vKsUwMIejGCUb1
fAnLTPfAY3lFCa+CmR89nE22dAoRDv+5RbnBsZ58BazPNJHrsVPRlfXB3OQmSQr0
SQIhANoJhs+xOJE/i8nJv0uAbzKyiD1YkvRkta0GpUOULyAVAiEAxaQus3E/SuqD
P7y5NeJnE7X6XkyC35zrsJRkz7orE8MCIHdDjsI8pjyNDeGqwUCDWE/a6DrmIDwe
emHSqMN2YvChAiEAnxLCM9NWaenOsaIoP+J1rDuvw+4499nJKVqGuVrSCRkCIEqK
4KSchPMc3x8M/uhw9oWTtKFmjA/PPh0FsWCdKrEy
-----END RSA PRIVATE KEY-----`)
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIIBjzCCATmgAwIBAgIRAKpi2WmTcFrVjxrl5n5YDUEwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
QQC9fEbRszP3t14Gr4oahV7zFObBI4TfA5i7YnlMXeLinb7MnvT4bkfOJzE6zktn
59zP7UiHs3l4YOuqrjiwM413AgMBAAGjaDBmMA4GA1UdDwEB/wQEAwICpDATBgNV
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MC4GA1UdEQQnMCWCC2V4
YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUA
A0EAUsVE6KMnza/ZbodLlyeMzdo7EM/5nb5ywyOxgIOCf0OOLHsPS9ueGLQX9HEG
//yjTXuhNcUugExIjM/AIwAZPQ==
-----END CERTIFICATE-----`)
// localhostKey is the private key for localhostCert.
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAL18RtGzM/e3XgavihqFXvMU5sEjhN8DmLtieUxd4uKdvsye9Phu
R84nMTrOS2fn3M/tSIezeXhg66quOLAzjXcCAwEAAQJBAKcRxH9wuglYLBdI/0OT
BLzfWPZCEw1vZmMR2FF1Fm8nkNOVDPleeVGTWoOEcYYlQbpTmkGSxJ6ya+hqRi6x
goECIQDx3+X49fwpL6B5qpJIJMyZBSCuMhH4B7JevhGGFENi3wIhAMiNJN5Q3UkL
IuSvv03kaPR5XVQ99/UeEetUgGvBcABpAiBJSBzVITIVCGkGc7d+RCf49KTCIklv
bGWObufAR8Ni4QIgWpILjW8dkGg8GOUZ0zaNA6Nvt6TIv2UWGJ4v5PoV98kCIQDx
rIiZs5QbKdycsv9gQJzwQAogC8o04X3Zz3dsoX+h4A==
-----END RSA PRIVATE KEY-----`)

View File

@ -0,0 +1,107 @@
/*
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 spdy
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync/atomic"
"k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/apimachinery/pkg/util/runtime"
)
const HeaderSpdy31 = "SPDY/3.1"
// responseUpgrader knows how to upgrade HTTP responses. It
// implements the httpstream.ResponseUpgrader interface.
type responseUpgrader struct {
}
// connWrapper is used to wrap a hijacked connection and its bufio.Reader. All
// calls will be handled directly by the underlying net.Conn with the exception
// of Read and Close calls, which will consider data in the bufio.Reader. This
// ensures that data already inside the used bufio.Reader instance is also
// read.
type connWrapper struct {
net.Conn
closed int32
bufReader *bufio.Reader
}
func (w *connWrapper) Read(b []byte) (n int, err error) {
if atomic.LoadInt32(&w.closed) == 1 {
return 0, io.EOF
}
return w.bufReader.Read(b)
}
func (w *connWrapper) Close() error {
err := w.Conn.Close()
atomic.StoreInt32(&w.closed, 1)
return err
}
// NewResponseUpgrader returns a new httpstream.ResponseUpgrader that is
// capable of upgrading HTTP responses using SPDY/3.1 via the
// spdystream package.
func NewResponseUpgrader() httpstream.ResponseUpgrader {
return responseUpgrader{}
}
// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
// streams. newStreamHandler will be called synchronously whenever the
// other end of the upgraded connection creates a new stream.
func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade))
if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "unable to upgrade: missing upgrade headers in request: %#v", req.Header)
return nil
}
hijacker, ok := w.(http.Hijacker)
if !ok {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "unable to upgrade: unable to hijack response")
return nil
}
w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31)
w.WriteHeader(http.StatusSwitchingProtocols)
conn, bufrw, err := hijacker.Hijack()
if err != nil {
runtime.HandleError(fmt.Errorf("unable to upgrade: error hijacking response: %v", err))
return nil
}
connWithBuf := &connWrapper{Conn: conn, bufReader: bufrw.Reader}
spdyConn, err := NewServerConnection(connWithBuf, newStreamHandler)
if err != nil {
runtime.HandleError(fmt.Errorf("unable to upgrade: error creating SPDY server connection: %v", err))
return nil
}
return spdyConn
}

View File

@ -0,0 +1,93 @@
/*
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 spdy
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestUpgradeResponse(t *testing.T) {
testCases := []struct {
connectionHeader string
upgradeHeader string
shouldError bool
}{
{
connectionHeader: "",
upgradeHeader: "",
shouldError: true,
},
{
connectionHeader: "Upgrade",
upgradeHeader: "",
shouldError: true,
},
{
connectionHeader: "",
upgradeHeader: "SPDY/3.1",
shouldError: true,
},
{
connectionHeader: "Upgrade",
upgradeHeader: "SPDY/3.1",
shouldError: false,
},
}
for i, testCase := range testCases {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
upgrader := NewResponseUpgrader()
conn := upgrader.UpgradeResponse(w, req, nil)
haveErr := conn == nil
if e, a := testCase.shouldError, haveErr; e != a {
t.Fatalf("%d: expected shouldErr=%t, got %t", i, testCase.shouldError, haveErr)
}
if haveErr {
return
}
if conn == nil {
t.Fatalf("%d: unexpected nil conn", i)
}
defer conn.Close()
}))
defer server.Close()
req, err := http.NewRequest("GET", server.URL, nil)
if err != nil {
t.Fatalf("%d: error creating request: %s", i, err)
}
req.Header.Set("Connection", testCase.connectionHeader)
req.Header.Set("Upgrade", testCase.upgradeHeader)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Fatalf("%d: unexpected non-nil err from client.Do: %s", i, err)
}
if testCase.shouldError {
continue
}
if resp.StatusCode != http.StatusSwitchingProtocols {
t.Fatalf("%d: expected status 101 switching protocols, got %d", i, resp.StatusCode)
}
}
}

View File

@ -0,0 +1,27 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["initialization.go"],
importpath = "k8s.io/apimachinery/pkg/util/initialization",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime: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

@ -0,0 +1,48 @@
/*
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 initialization
import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// IsInitialized returns if the initializers indicates means initialized.
func IsInitialized(initializers *metav1.Initializers) bool {
if initializers == nil {
return true
}
// Persisted objects will never be in this state. The initializer admission
// plugin will override metadata.initializers to nil. If the initializer
// admissio plugin is disabled, the generic registry always set
// metadata.initializers to nil. However, this function
// might be called before the object persisted, thus the check.
if len(initializers.Pending) == 0 && initializers.Result == nil {
return true
}
return false
}
// IsObjectInitialized returns if the object is initialized.
func IsObjectInitialized(obj runtime.Object) (bool, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return false, err
}
return IsInitialized(accessor.GetInitializers()), nil
}

50
vendor/k8s.io/apimachinery/pkg/util/intstr/BUILD generated vendored Normal file
View File

@ -0,0 +1,50 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["intstr_test.go"],
importpath = "k8s.io/apimachinery/pkg/util/intstr",
library = ":go_default_library",
deps = ["//vendor/github.com/ghodss/yaml:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"generated.pb.go",
"intstr.go",
],
importpath = "k8s.io/apimachinery/pkg/util/intstr",
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/google/gofuzz:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
filegroup(
name = "go_default_library_protos",
srcs = ["generated.proto"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,381 @@
/*
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.
*/
// Code generated by protoc-gen-gogo.
// source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/intstr/generated.proto
// DO NOT EDIT!
/*
Package intstr is a generated protocol buffer package.
It is generated from these files:
k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/intstr/generated.proto
It has these top-level messages:
IntOrString
*/
package intstr
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
func (m *IntOrString) Reset() { *m = IntOrString{} }
func (*IntOrString) ProtoMessage() {}
func (*IntOrString) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} }
func init() {
proto.RegisterType((*IntOrString)(nil), "k8s.io.apimachinery.pkg.util.intstr.IntOrString")
}
func (m *IntOrString) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *IntOrString) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
dAtA[i] = 0x8
i++
i = encodeVarintGenerated(dAtA, i, uint64(m.Type))
dAtA[i] = 0x10
i++
i = encodeVarintGenerated(dAtA, i, uint64(m.IntVal))
dAtA[i] = 0x1a
i++
i = encodeVarintGenerated(dAtA, i, uint64(len(m.StrVal)))
i += copy(dAtA[i:], m.StrVal)
return i, nil
}
func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *IntOrString) Size() (n int) {
var l int
_ = l
n += 1 + sovGenerated(uint64(m.Type))
n += 1 + sovGenerated(uint64(m.IntVal))
l = len(m.StrVal)
n += 1 + l + sovGenerated(uint64(l))
return n
}
func sovGenerated(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozGenerated(x uint64) (n int) {
return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *IntOrString) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: IntOrString: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: IntOrString: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
}
m.Type = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Type |= (Type(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field IntVal", wireType)
}
m.IntVal = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.IntVal |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field StrVal", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.StrVal = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipGenerated(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowGenerated
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowGenerated
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowGenerated
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthGenerated
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowGenerated
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipGenerated(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow")
)
func init() {
proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/intstr/generated.proto", fileDescriptorGenerated)
}
var fileDescriptorGenerated = []byte{
// 292 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0x31, 0x4b, 0x33, 0x31,
0x1c, 0xc6, 0x93, 0xb7, 0x7d, 0x8b, 0x9e, 0xe0, 0x50, 0x1c, 0x8a, 0x43, 0x7a, 0x28, 0xc8, 0x0d,
0x9a, 0xac, 0xe2, 0xd8, 0xad, 0x20, 0x08, 0x57, 0x71, 0x70, 0xbb, 0x6b, 0x63, 0x1a, 0xae, 0x4d,
0x42, 0xee, 0x7f, 0xc2, 0x6d, 0xfd, 0x08, 0xba, 0x39, 0xfa, 0x71, 0x6e, 0xec, 0xd8, 0x41, 0x8a,
0x17, 0xbf, 0x85, 0x93, 0x5c, 0xee, 0x40, 0xa7, 0xe4, 0x79, 0x9e, 0xdf, 0x2f, 0x90, 0xe0, 0x36,
0xbb, 0xce, 0xa9, 0xd4, 0x2c, 0x2b, 0x52, 0x6e, 0x15, 0x07, 0x9e, 0xb3, 0x67, 0xae, 0x16, 0xda,
0xb2, 0x6e, 0x48, 0x8c, 0x5c, 0x27, 0xf3, 0xa5, 0x54, 0xdc, 0x96, 0xcc, 0x64, 0x82, 0x15, 0x20,
0x57, 0x4c, 0x2a, 0xc8, 0xc1, 0x32, 0xc1, 0x15, 0xb7, 0x09, 0xf0, 0x05, 0x35, 0x56, 0x83, 0x1e,
0x9e, 0xb7, 0x12, 0xfd, 0x2b, 0x51, 0x93, 0x09, 0xda, 0x48, 0xb4, 0x95, 0x4e, 0xaf, 0x84, 0x84,
0x65, 0x91, 0xd2, 0xb9, 0x5e, 0x33, 0xa1, 0x85, 0x66, 0xde, 0x4d, 0x8b, 0x27, 0x9f, 0x7c, 0xf0,
0xb7, 0xf6, 0xcd, 0xb3, 0x57, 0x1c, 0x1c, 0x4d, 0x15, 0xdc, 0xd9, 0x19, 0x58, 0xa9, 0xc4, 0x30,
0x0a, 0xfa, 0x50, 0x1a, 0x3e, 0xc2, 0x21, 0x8e, 0x7a, 0x93, 0x93, 0x6a, 0x3f, 0x46, 0x6e, 0x3f,
0xee, 0xdf, 0x97, 0x86, 0x7f, 0x77, 0x67, 0xec, 0x89, 0xe1, 0x45, 0x30, 0x90, 0x0a, 0x1e, 0x92,
0xd5, 0xe8, 0x5f, 0x88, 0xa3, 0xff, 0x93, 0xe3, 0x8e, 0x1d, 0x4c, 0x7d, 0x1b, 0x77, 0x6b, 0xc3,
0xe5, 0x60, 0x1b, 0xae, 0x17, 0xe2, 0xe8, 0xf0, 0x97, 0x9b, 0xf9, 0x36, 0xee, 0xd6, 0x9b, 0x83,
0xb7, 0xf7, 0x31, 0xda, 0x7c, 0x84, 0x68, 0x72, 0x59, 0xd5, 0x04, 0x6d, 0x6b, 0x82, 0x76, 0x35,
0x41, 0x1b, 0x47, 0x70, 0xe5, 0x08, 0xde, 0x3a, 0x82, 0x77, 0x8e, 0xe0, 0x4f, 0x47, 0xf0, 0xcb,
0x17, 0x41, 0x8f, 0x83, 0xf6, 0xc3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x52, 0xa0, 0xb5, 0xc9,
0x64, 0x01, 0x00, 0x00,
}

View File

@ -0,0 +1,43 @@
/*
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.
*/
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
syntax = 'proto2';
package k8s.io.apimachinery.pkg.util.intstr;
// Package-wide variables from generator "generated".
option go_package = "intstr";
// IntOrString is a type that can hold an int32 or a string. When used in
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +k8s:openapi-gen=true
message IntOrString {
optional int64 type = 1;
optional int32 intVal = 2;
optional string strVal = 3;
}

177
vendor/k8s.io/apimachinery/pkg/util/intstr/intstr.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
/*
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 intstr
import (
"encoding/json"
"fmt"
"math"
"runtime/debug"
"strconv"
"strings"
openapi "k8s.io/kube-openapi/pkg/common"
"github.com/go-openapi/spec"
"github.com/golang/glog"
"github.com/google/gofuzz"
)
// IntOrString is a type that can hold an int32 or a string. When used in
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +k8s:openapi-gen=true
type IntOrString struct {
Type Type `protobuf:"varint,1,opt,name=type,casttype=Type"`
IntVal int32 `protobuf:"varint,2,opt,name=intVal"`
StrVal string `protobuf:"bytes,3,opt,name=strVal"`
}
// Type represents the stored type of IntOrString.
type Type int
const (
Int Type = iota // The IntOrString holds an int.
String // The IntOrString holds a string.
)
// FromInt creates an IntOrString object with an int32 value. It is
// your responsibility not to call this method with a value greater
// than int32.
// TODO: convert to (val int32)
func FromInt(val int) IntOrString {
if val > math.MaxInt32 || val < math.MinInt32 {
glog.Errorf("value: %d overflows int32\n%s\n", val, debug.Stack())
}
return IntOrString{Type: Int, IntVal: int32(val)}
}
// FromString creates an IntOrString object with a string value.
func FromString(val string) IntOrString {
return IntOrString{Type: String, StrVal: val}
}
// Parse the given string and try to convert it to an integer before
// setting it as a string value.
func Parse(val string) IntOrString {
i, err := strconv.Atoi(val)
if err != nil {
return FromString(val)
}
return FromInt(i)
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
if value[0] == '"' {
intstr.Type = String
return json.Unmarshal(value, &intstr.StrVal)
}
intstr.Type = Int
return json.Unmarshal(value, &intstr.IntVal)
}
// String returns the string value, or the Itoa of the int value.
func (intstr *IntOrString) String() string {
if intstr.Type == String {
return intstr.StrVal
}
return strconv.Itoa(intstr.IntValue())
}
// IntValue returns the IntVal if type Int, or if
// it is a String, will attempt a conversion to int.
func (intstr *IntOrString) IntValue() int {
if intstr.Type == String {
i, _ := strconv.Atoi(intstr.StrVal)
return i
}
return int(intstr.IntVal)
}
// MarshalJSON implements the json.Marshaller interface.
func (intstr IntOrString) MarshalJSON() ([]byte, error) {
switch intstr.Type {
case Int:
return json.Marshal(intstr.IntVal)
case String:
return json.Marshal(intstr.StrVal)
default:
return []byte{}, fmt.Errorf("impossible IntOrString.Type")
}
}
func (_ IntOrString) OpenAPIDefinition() openapi.OpenAPIDefinition {
return openapi.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "int-or-string",
},
},
}
}
func (intstr *IntOrString) Fuzz(c fuzz.Continue) {
if intstr == nil {
return
}
if c.RandBool() {
intstr.Type = Int
c.Fuzz(&intstr.IntVal)
intstr.StrVal = ""
} else {
intstr.Type = String
intstr.IntVal = 0
c.Fuzz(&intstr.StrVal)
}
}
func GetValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
value, isPercent, err := getIntOrPercentValue(intOrPercent)
if err != nil {
return 0, fmt.Errorf("invalid value for IntOrString: %v", err)
}
if isPercent {
if roundUp {
value = int(math.Ceil(float64(value) * (float64(total)) / 100))
} else {
value = int(math.Floor(float64(value) * (float64(total)) / 100))
}
}
return value, nil
}
func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) {
switch intOrStr.Type {
case Int:
return intOrStr.IntValue(), false, nil
case String:
s := strings.Replace(intOrStr.StrVal, "%", "", -1)
v, err := strconv.Atoi(s)
if err != nil {
return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err)
}
return int(v), true, nil
}
return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
}

View File

@ -0,0 +1,176 @@
/*
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 intstr
import (
"encoding/json"
"reflect"
"testing"
"github.com/ghodss/yaml"
)
func TestFromInt(t *testing.T) {
i := FromInt(93)
if i.Type != Int || i.IntVal != 93 {
t.Errorf("Expected IntVal=93, got %+v", i)
}
}
func TestFromString(t *testing.T) {
i := FromString("76")
if i.Type != String || i.StrVal != "76" {
t.Errorf("Expected StrVal=\"76\", got %+v", i)
}
}
type IntOrStringHolder struct {
IOrS IntOrString `json:"val"`
}
func TestIntOrStringUnmarshalJSON(t *testing.T) {
cases := []struct {
input string
result IntOrString
}{
{"{\"val\": 123}", FromInt(123)},
{"{\"val\": \"123\"}", FromString("123")},
}
for _, c := range cases {
var result IntOrStringHolder
if err := json.Unmarshal([]byte(c.input), &result); err != nil {
t.Errorf("Failed to unmarshal input '%v': %v", c.input, err)
}
if result.IOrS != c.result {
t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result)
}
}
}
func TestIntOrStringMarshalJSON(t *testing.T) {
cases := []struct {
input IntOrString
result string
}{
{FromInt(123), "{\"val\":123}"},
{FromString("123"), "{\"val\":\"123\"}"},
}
for _, c := range cases {
input := IntOrStringHolder{c.input}
result, err := json.Marshal(&input)
if err != nil {
t.Errorf("Failed to marshal input '%v': %v", input, err)
}
if string(result) != c.result {
t.Errorf("Failed to marshal input '%v': expected: %+v, got %q", input, c.result, string(result))
}
}
}
func TestIntOrStringMarshalJSONUnmarshalYAML(t *testing.T) {
cases := []struct {
input IntOrString
}{
{FromInt(123)},
{FromString("123")},
}
for _, c := range cases {
input := IntOrStringHolder{c.input}
jsonMarshalled, err := json.Marshal(&input)
if err != nil {
t.Errorf("1: Failed to marshal input: '%v': %v", input, err)
}
var result IntOrStringHolder
err = yaml.Unmarshal(jsonMarshalled, &result)
if err != nil {
t.Errorf("2: Failed to unmarshal '%+v': %v", string(jsonMarshalled), err)
}
if !reflect.DeepEqual(input, result) {
t.Errorf("3: Failed to marshal input '%+v': got %+v", input, result)
}
}
}
func TestGetValueFromIntOrPercent(t *testing.T) {
tests := []struct {
input IntOrString
total int
roundUp bool
expectErr bool
expectVal int
}{
{
input: FromInt(123),
expectErr: false,
expectVal: 123,
},
{
input: FromString("90%"),
total: 100,
roundUp: true,
expectErr: false,
expectVal: 90,
},
{
input: FromString("90%"),
total: 95,
roundUp: true,
expectErr: false,
expectVal: 86,
},
{
input: FromString("90%"),
total: 95,
roundUp: false,
expectErr: false,
expectVal: 85,
},
{
input: FromString("%"),
expectErr: true,
},
{
input: FromString("90#"),
expectErr: true,
},
{
input: FromString("#%"),
expectErr: true,
},
}
for i, test := range tests {
t.Logf("test case %d", i)
value, err := GetValueFromIntOrPercent(&test.input, test.total, test.roundUp)
if test.expectErr && err == nil {
t.Errorf("expected error, but got none")
continue
}
if !test.expectErr && err != nil {
t.Errorf("unexpected err: %v", err)
continue
}
if test.expectVal != value {
t.Errorf("expected %v, but got %v", test.expectVal, value)
}
}
}

33
vendor/k8s.io/apimachinery/pkg/util/json/BUILD generated vendored Normal file
View File

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

119
vendor/k8s.io/apimachinery/pkg/util/json/json.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
/*
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 json
import (
"bytes"
"encoding/json"
"io"
)
// NewEncoder delegates to json.NewEncoder
// It is only here so this package can be a drop-in for common encoding/json uses
func NewEncoder(w io.Writer) *json.Encoder {
return json.NewEncoder(w)
}
// Marshal delegates to json.Marshal
// It is only here so this package can be a drop-in for common encoding/json uses
func Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
// Unmarshal unmarshals the given data
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
func Unmarshal(data []byte, v interface{}) error {
switch v := v.(type) {
case *map[string]interface{}:
// Build a decoder from the given data
decoder := json.NewDecoder(bytes.NewBuffer(data))
// Preserve numbers, rather than casting to float64 automatically
decoder.UseNumber()
// Run the decode
if err := decoder.Decode(v); err != nil {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertMapNumbers(*v)
case *[]interface{}:
// Build a decoder from the given data
decoder := json.NewDecoder(bytes.NewBuffer(data))
// Preserve numbers, rather than casting to float64 automatically
decoder.UseNumber()
// Run the decode
if err := decoder.Decode(v); err != nil {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertSliceNumbers(*v)
default:
return json.Unmarshal(data, v)
}
}
// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
// values which are map[string]interface{} or []interface{} are recursively visited
func convertMapNumbers(m map[string]interface{}) error {
var err error
for k, v := range m {
switch v := v.(type) {
case json.Number:
m[k], err = convertNumber(v)
case map[string]interface{}:
err = convertMapNumbers(v)
case []interface{}:
err = convertSliceNumbers(v)
}
if err != nil {
return err
}
}
return nil
}
// convertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
// values which are map[string]interface{} or []interface{} are recursively visited
func convertSliceNumbers(s []interface{}) error {
var err error
for i, v := range s {
switch v := v.(type) {
case json.Number:
s[i], err = convertNumber(v)
case map[string]interface{}:
err = convertMapNumbers(v)
case []interface{}:
err = convertSliceNumbers(v)
}
if err != nil {
return err
}
}
return nil
}
// convertNumber converts a json.Number to an int64 or float64, or returns an error
func convertNumber(n json.Number) (interface{}, error) {
// Attempt to convert to an int64 first
if i, err := n.Int64(); err == nil {
return i, nil
}
// Return a float64 (default json.Decode() behavior)
// An overflow will return an error
return n.Float64()
}

319
vendor/k8s.io/apimachinery/pkg/util/json/json_test.go generated vendored Normal file
View File

@ -0,0 +1,319 @@
// +build go1.8
/*
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 json
import (
"fmt"
"math"
"reflect"
"strconv"
"strings"
"testing"
)
func TestEvaluateTypes(t *testing.T) {
testCases := []struct {
In string
Data interface{}
Out string
Err bool
}{
// Invalid syntaxes
{
In: `x`,
Err: true,
},
{
In: ``,
Err: true,
},
// Null
{
In: `null`,
Data: nil,
Out: `null`,
},
// Booleans
{
In: `true`,
Data: true,
Out: `true`,
},
{
In: `false`,
Data: false,
Out: `false`,
},
// Integers
{
In: `0`,
Data: int64(0),
Out: `0`,
},
{
In: `-0`,
Data: int64(-0),
Out: `0`,
},
{
In: `1`,
Data: int64(1),
Out: `1`,
},
{
In: `2147483647`,
Data: int64(math.MaxInt32),
Out: `2147483647`,
},
{
In: `-2147483648`,
Data: int64(math.MinInt32),
Out: `-2147483648`,
},
{
In: `9223372036854775807`,
Data: int64(math.MaxInt64),
Out: `9223372036854775807`,
},
{
In: `-9223372036854775808`,
Data: int64(math.MinInt64),
Out: `-9223372036854775808`,
},
// Int overflow
{
In: `9223372036854775808`, // MaxInt64 + 1
Data: float64(9223372036854775808),
Out: `9223372036854776000`,
},
{
In: `-9223372036854775809`, // MinInt64 - 1
Data: float64(math.MinInt64),
Out: `-9223372036854776000`,
},
// Floats
{
In: `0.0`,
Data: float64(0),
Out: `0`,
},
{
In: `-0.0`,
Data: float64(-0.0),
Out: `-0`,
},
{
In: `0.5`,
Data: float64(0.5),
Out: `0.5`,
},
{
In: `1e3`,
Data: float64(1e3),
Out: `1000`,
},
{
In: `1.5`,
Data: float64(1.5),
Out: `1.5`,
},
{
In: `-0.3`,
Data: float64(-.3),
Out: `-0.3`,
},
{
// Largest representable float32
In: `3.40282346638528859811704183484516925440e+38`,
Data: float64(math.MaxFloat32),
Out: strconv.FormatFloat(math.MaxFloat32, 'g', -1, 64),
},
{
// Smallest float32 without losing precision
In: `1.175494351e-38`,
Data: float64(1.175494351e-38),
Out: `1.175494351e-38`,
},
{
// float32 closest to zero
In: `1.401298464324817070923729583289916131280e-45`,
Data: float64(math.SmallestNonzeroFloat32),
Out: strconv.FormatFloat(math.SmallestNonzeroFloat32, 'g', -1, 64),
},
{
// Largest representable float64
In: `1.797693134862315708145274237317043567981e+308`,
Data: float64(math.MaxFloat64),
Out: strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64),
},
{
// Closest to zero without losing precision
In: `2.2250738585072014e-308`,
Data: float64(2.2250738585072014e-308),
Out: `2.2250738585072014e-308`,
},
{
// float64 closest to zero
In: `4.940656458412465441765687928682213723651e-324`,
Data: float64(math.SmallestNonzeroFloat64),
Out: strconv.FormatFloat(math.SmallestNonzeroFloat64, 'g', -1, 64),
},
{
// math.MaxFloat64 + 2 overflow
In: `1.7976931348623159e+308`,
Err: true,
},
// Strings
{
In: `""`,
Data: string(""),
Out: `""`,
},
{
In: `"0"`,
Data: string("0"),
Out: `"0"`,
},
{
In: `"A"`,
Data: string("A"),
Out: `"A"`,
},
{
In: `"Iñtërnâtiônàlizætiøn"`,
Data: string("Iñtërnâtiônàlizætiøn"),
Out: `"Iñtërnâtiônàlizætiøn"`,
},
// Arrays
{
In: `[]`,
Data: []interface{}{},
Out: `[]`,
},
{
In: `[` + strings.Join([]string{
`null`,
`true`,
`false`,
`0`,
`9223372036854775807`,
`0.0`,
`0.5`,
`1.0`,
`1.797693134862315708145274237317043567981e+308`,
`"0"`,
`"A"`,
`"Iñtërnâtiônàlizætiøn"`,
`[null,true,1,1.0,1.5]`,
`{"boolkey":true,"floatkey":1.0,"intkey":1,"nullkey":null}`,
}, ",") + `]`,
Data: []interface{}{
nil,
true,
false,
int64(0),
int64(math.MaxInt64),
float64(0.0),
float64(0.5),
float64(1.0),
float64(math.MaxFloat64),
string("0"),
string("A"),
string("Iñtërnâtiônàlizætiøn"),
[]interface{}{nil, true, int64(1), float64(1.0), float64(1.5)},
map[string]interface{}{"nullkey": nil, "boolkey": true, "intkey": int64(1), "floatkey": float64(1.0)},
},
Out: `[` + strings.Join([]string{
`null`,
`true`,
`false`,
`0`,
`9223372036854775807`,
`0`,
`0.5`,
`1`,
strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64),
`"0"`,
`"A"`,
`"Iñtërnâtiônàlizætiøn"`,
`[null,true,1,1,1.5]`,
`{"boolkey":true,"floatkey":1,"intkey":1,"nullkey":null}`, // gets alphabetized by Marshal
}, ",") + `]`,
},
// Maps
{
In: `{}`,
Data: map[string]interface{}{},
Out: `{}`,
},
{
In: `{"boolkey":true,"floatkey":1.0,"intkey":1,"nullkey":null}`,
Data: map[string]interface{}{"nullkey": nil, "boolkey": true, "intkey": int64(1), "floatkey": float64(1.0)},
Out: `{"boolkey":true,"floatkey":1,"intkey":1,"nullkey":null}`, // gets alphabetized by Marshal
},
}
for _, tc := range testCases {
inputJSON := fmt.Sprintf(`{"data":%s}`, tc.In)
expectedJSON := fmt.Sprintf(`{"data":%s}`, tc.Out)
m := map[string]interface{}{}
err := Unmarshal([]byte(inputJSON), &m)
if tc.Err && err != nil {
// Expected error
continue
}
if err != nil {
t.Errorf("%s: error decoding: %v", tc.In, err)
continue
}
if tc.Err {
t.Errorf("%s: expected error, got none", tc.In)
continue
}
data, ok := m["data"]
if !ok {
t.Errorf("%s: decoded object missing data key: %#v", tc.In, m)
continue
}
if !reflect.DeepEqual(tc.Data, data) {
t.Errorf("%s: expected\n\t%#v (%v), got\n\t%#v (%v)", tc.In, tc.Data, reflect.TypeOf(tc.Data), data, reflect.TypeOf(data))
continue
}
outputJSON, err := Marshal(m)
if err != nil {
t.Errorf("%s: error encoding: %v", tc.In, err)
continue
}
if expectedJSON != string(outputJSON) {
t.Errorf("%s: expected\n\t%s, got\n\t%s", tc.In, expectedJSON, string(outputJSON))
continue
}
}
}

View File

@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["patch_test.go"],
importpath = "k8s.io/apimachinery/pkg/util/jsonmergepatch",
library = ":go_default_library",
deps = [
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["patch.go"],
importpath = "k8s.io/apimachinery/pkg/util/jsonmergepatch",
deps = [
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch: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

@ -0,0 +1,144 @@
/*
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 jsonmergepatch
import (
"fmt"
"reflect"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/mergepatch"
)
// Create a 3-way merge patch based-on JSON merge patch.
// Calculate addition-and-change patch between current and modified.
// Calculate deletion patch between original and modified.
func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
if len(original) == 0 {
original = []byte(`{}`)
}
if len(modified) == 0 {
modified = []byte(`{}`)
}
if len(current) == 0 {
current = []byte(`{}`)
}
addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified)
if err != nil {
return nil, err
}
// Only keep addition and changes
addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false)
if err != nil {
return nil, err
}
deletePatch, err := jsonpatch.CreateMergePatch(original, modified)
if err != nil {
return nil, err
}
// Only keep deletion
deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true)
if err != nil {
return nil, err
}
hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj)
if err != nil {
return nil, err
}
if hasConflicts {
return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj))
}
patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch)
if err != nil {
return nil, err
}
var patchMap map[string]interface{}
err = json.Unmarshal(patch, &patchMap)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal patch for precondition check: %s", patch)
}
meetPreconditions, err := meetPreconditions(patchMap, fns...)
if err != nil {
return nil, err
}
if !meetPreconditions {
return nil, mergepatch.NewErrPreconditionFailed(patchMap)
}
return patch, nil
}
// keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean.
// It returns a filtered object and its corresponding json-encoded byte array.
// It is a wrapper of func keepOrDeleteNullInObj
func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) {
var patchMap map[string]interface{}
err := json.Unmarshal(patch, &patchMap)
if err != nil {
return nil, nil, err
}
filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull)
if err != nil {
return nil, nil, err
}
o, err := json.Marshal(filteredMap)
return o, filteredMap, err
}
// keepOrDeleteNullInObj will keep only the null value and delete all the others,
// if keepNull is true. Otherwise, it will delete all the null value and keep the others.
func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) {
filteredMap := make(map[string]interface{})
var err error
for key, val := range m {
switch {
case keepNull && val == nil:
filteredMap[key] = nil
case val != nil:
switch typedVal := val.(type) {
case map[string]interface{}:
filteredMap[key], err = keepOrDeleteNullInObj(typedVal, keepNull)
if err != nil {
return nil, err
}
case []interface{}, string, float64, bool, int, int64, nil:
// Lists are always replaced in Json, no need to check each entry in the list.
if !keepNull {
filteredMap[key] = val
}
default:
return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal))
}
}
}
return filteredMap, nil
}
func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) {
// Apply the preconditions to the patch, and return an error if any of them fail.
for _, fn := range fns {
if !fn(patchObj) {
return false, fmt.Errorf("precondition failed for: %v", patchObj)
}
}
return true, nil
}

View File

@ -0,0 +1,663 @@
/*
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 jsonmergepatch
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/evanphx/json-patch"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/util/json"
)
type FilterNullTestCases struct {
TestCases []FilterNullTestCase
}
type FilterNullTestCase struct {
Description string
OriginalObj map[string]interface{}
ExpectedWithNull map[string]interface{}
ExpectedWithoutNull map[string]interface{}
}
var filterNullTestCaseData = []byte(`
testCases:
- description: nil original
originalObj: {}
expectedWithNull: {}
expectedWithoutNull: {}
- description: simple map
originalObj:
nilKey: null
nonNilKey: foo
expectedWithNull:
nilKey: null
expectedWithoutNull:
nonNilKey: foo
- description: simple map with all nil values
originalObj:
nilKey1: null
nilKey2: null
expectedWithNull:
nilKey1: null
nilKey2: null
expectedWithoutNull: {}
- description: simple map with all non-nil values
originalObj:
nonNilKey: foo
nonNilKey: bar
expectedWithNull: {}
expectedWithoutNull:
nonNilKey: foo
nonNilKey: bar
- description: nested map
originalObj:
mapKey:
nilKey: null
nonNilKey: foo
expectedWithNull:
mapKey:
nilKey: null
expectedWithoutNull:
mapKey:
nonNilKey: foo
- description: nested map that all subkeys are nil
originalObj:
mapKey:
nilKey1: null
nilKey2: null
expectedWithNull:
mapKey:
nilKey1: null
nilKey2: null
expectedWithoutNull:
mapKey: {}
- description: nested map that all subkeys are non-nil
originalObj:
mapKey:
nonNilKey: foo
nonNilKey: bar
expectedWithNull:
mapKey: {}
expectedWithoutNull:
mapKey:
nonNilKey: foo
nonNilKey: bar
- description: empty list
originalObj:
listKey: []
expectedWithNull: {}
expectedWithoutNull:
listKey: []
- description: list of primitives
originalObj:
listKey:
- 1
- 2
expectedWithNull: {}
expectedWithoutNull:
listKey:
- 1
- 2
- description: list of maps
originalObj:
listKey:
- k1: v1
- k2: null
- k3: v3
k4: null
expectedWithNull: {}
expectedWithoutNull:
listKey:
- k1: v1
- k2: null
- k3: v3
k4: null
- description: list of different types
originalObj:
listKey:
- k1: v1
- k2: null
- v3
expectedWithNull: {}
expectedWithoutNull:
listKey:
- k1: v1
- k2: null
- v3
`)
func TestKeepOrDeleteNullInObj(t *testing.T) {
tc := FilterNullTestCases{}
err := yaml.Unmarshal(filterNullTestCaseData, &tc)
if err != nil {
t.Fatalf("can't unmarshal test cases: %s\n", err)
}
for _, test := range tc.TestCases {
resultWithNull, err := keepOrDeleteNullInObj(test.OriginalObj, true)
if err != nil {
t.Errorf("Failed in test case %q when trying to keep null values: %s", test.Description, err)
}
if !reflect.DeepEqual(test.ExpectedWithNull, resultWithNull) {
t.Errorf("Failed in test case %q when trying to keep null values:\nexpected expectedWithNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithNull, resultWithNull)
}
resultWithoutNull, err := keepOrDeleteNullInObj(test.OriginalObj, false)
if err != nil {
t.Errorf("Failed in test case %q when trying to keep non-null values: %s", test.Description, err)
}
if !reflect.DeepEqual(test.ExpectedWithoutNull, resultWithoutNull) {
t.Errorf("Failed in test case %q when trying to keep non-null values:\n expected expectedWithoutNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithoutNull, resultWithoutNull)
}
}
}
type JSONMergePatchTestCases struct {
TestCases []JSONMergePatchTestCase
}
type JSONMergePatchTestCase struct {
Description string
JSONMergePatchTestCaseData
}
type JSONMergePatchTestCaseData struct {
// Original is the original object (last-applied config in annotation)
Original map[string]interface{}
// Modified is the modified object (new config we want)
Modified map[string]interface{}
// Current is the current object (live config in the server)
Current map[string]interface{}
// ThreeWay is the expected three-way merge patch
ThreeWay map[string]interface{}
// Result is the expected object after applying the three-way patch on current object.
Result map[string]interface{}
}
var createJSONMergePatchTestCaseData = []byte(`
testCases:
- description: nil original
modified:
name: 1
value: 1
current:
name: 1
other: a
threeWay:
value: 1
result:
name: 1
value: 1
other: a
- description: nil patch
original:
name: 1
modified:
name: 1
current:
name: 1
threeWay:
{}
result:
name: 1
- description: add field to map
original:
name: 1
modified:
name: 1
value: 1
current:
name: 1
other: a
threeWay:
value: 1
result:
name: 1
value: 1
other: a
- description: add field to map with conflict
original:
name: 1
modified:
name: 1
value: 1
current:
name: a
other: a
threeWay:
name: 1
value: 1
result:
name: 1
value: 1
other: a
- description: add field and delete field from map
original:
name: 1
modified:
value: 1
current:
name: 1
other: a
threeWay:
name: null
value: 1
result:
value: 1
other: a
- description: add field and delete field from map with conflict
original:
name: 1
modified:
value: 1
current:
name: a
other: a
threeWay:
name: null
value: 1
result:
value: 1
other: a
- description: delete field from nested map
original:
simpleMap:
key1: 1
key2: 1
modified:
simpleMap:
key1: 1
current:
simpleMap:
key1: 1
key2: 1
other: a
threeWay:
simpleMap:
key2: null
result:
simpleMap:
key1: 1
other: a
- description: delete field from nested map with conflict
original:
simpleMap:
key1: 1
key2: 1
modified:
simpleMap:
key1: 1
current:
simpleMap:
key1: a
key2: 1
other: a
threeWay:
simpleMap:
key1: 1
key2: null
result:
simpleMap:
key1: 1
other: a
- description: delete all fields from map
original:
name: 1
value: 1
modified: {}
current:
name: 1
value: 1
other: a
threeWay:
name: null
value: null
result:
other: a
- description: delete all fields from map with conflict
original:
name: 1
value: 1
modified: {}
current:
name: 1
value: a
other: a
threeWay:
name: null
value: null
result:
other: a
- description: add field and delete all fields from map
original:
name: 1
value: 1
modified:
other: a
current:
name: 1
value: 1
other: a
threeWay:
name: null
value: null
result:
other: a
- description: add field and delete all fields from map with conflict
original:
name: 1
value: 1
modified:
other: a
current:
name: 1
value: 1
other: b
threeWay:
name: null
value: null
other: a
result:
other: a
- description: replace list of scalars
original:
intList:
- 1
- 2
modified:
intList:
- 2
- 3
current:
intList:
- 1
- 2
threeWay:
intList:
- 2
- 3
result:
intList:
- 2
- 3
- description: replace list of scalars with conflict
original:
intList:
- 1
- 2
modified:
intList:
- 2
- 3
current:
intList:
- 1
- 4
threeWay:
intList:
- 2
- 3
result:
intList:
- 2
- 3
- description: patch with different scalar type
original:
foo: 1
modified:
foo: true
current:
foo: 1
bar: 2
threeWay:
foo: true
result:
foo: true
bar: 2
- description: patch from scalar to list
original:
foo: 0
modified:
foo:
- 1
- 2
current:
foo: 0
bar: 2
threeWay:
foo:
- 1
- 2
result:
foo:
- 1
- 2
bar: 2
- description: patch from list to scalar
original:
foo:
- 1
- 2
modified:
foo: 0
current:
foo:
- 1
- 2
bar: 2
threeWay:
foo: 0
result:
foo: 0
bar: 2
- description: patch from scalar to map
original:
foo: 0
modified:
foo:
baz: 1
current:
foo: 0
bar: 2
threeWay:
foo:
baz: 1
result:
foo:
baz: 1
bar: 2
- description: patch from map to scalar
original:
foo:
baz: 1
modified:
foo: 0
current:
foo:
baz: 1
bar: 2
threeWay:
foo: 0
result:
foo: 0
bar: 2
- description: patch from map to list
original:
foo:
baz: 1
modified:
foo:
- 1
- 2
current:
foo:
baz: 1
bar: 2
threeWay:
foo:
- 1
- 2
result:
foo:
- 1
- 2
bar: 2
- description: patch from list to map
original:
foo:
- 1
- 2
modified:
foo:
baz: 0
current:
foo:
- 1
- 2
bar: 2
threeWay:
foo:
baz: 0
result:
foo:
baz: 0
bar: 2
- description: patch with different nested types
original:
foo:
- a: true
- 2
- false
modified:
foo:
- 1
- false
- b: 1
current:
foo:
- a: true
- 2
- false
bar: 0
threeWay:
foo:
- 1
- false
- b: 1
result:
foo:
- 1
- false
- b: 1
bar: 0
`)
func TestCreateThreeWayJSONMergePatch(t *testing.T) {
tc := JSONMergePatchTestCases{}
err := yaml.Unmarshal(createJSONMergePatchTestCaseData, &tc)
if err != nil {
t.Errorf("can't unmarshal test cases: %s\n", err)
return
}
for _, c := range tc.TestCases {
testThreeWayPatch(t, c)
}
}
func testThreeWayPatch(t *testing.T, c JSONMergePatchTestCase) {
original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c)
actual, err := CreateThreeWayJSONMergePatch(original, modified, current)
if err != nil {
t.Fatalf("error: %s", err)
}
testPatchCreation(t, expected, actual, c.Description)
testPatchApplication(t, current, actual, result, c.Description)
}
func testPatchCreation(t *testing.T, expected, actual []byte, description string) {
if !reflect.DeepEqual(actual, expected) {
t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n",
description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual))
return
}
}
func testPatchApplication(t *testing.T, original, patch, expected []byte, description string) {
result, err := jsonpatch.MergePatch(original, patch)
if err != nil {
t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n",
err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original))
return
}
if !reflect.DeepEqual(result, expected) {
format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n"
t.Errorf(format, description,
jsonToYAMLOrError(original), jsonToYAMLOrError(patch),
jsonToYAMLOrError(expected), jsonToYAMLOrError(result))
return
}
}
func threeWayTestCaseToJSONOrFail(t *testing.T, c JSONMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) {
return testObjectToJSONOrFail(t, c.Original),
testObjectToJSONOrFail(t, c.Modified),
testObjectToJSONOrFail(t, c.Current),
testObjectToJSONOrFail(t, c.ThreeWay),
testObjectToJSONOrFail(t, c.Result)
}
func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte {
if o == nil {
return nil
}
j, err := toJSON(o)
if err != nil {
t.Error(err)
}
return j
}
func jsonToYAMLOrError(j []byte) string {
y, err := jsonToYAML(j)
if err != nil {
return err.Error()
}
return string(y)
}
func toJSON(v interface{}) ([]byte, error) {
j, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("json marshal failed: %v\n%v\n", err, spew.Sdump(v))
}
return j, nil
}
func jsonToYAML(j []byte) ([]byte, error) {
y, err := yaml.JSONToYAML(j)
if err != nil {
return nil, fmt.Errorf("json to yaml failed: %v\n%v\n", err, j)
}
return y, nil
}

40
vendor/k8s.io/apimachinery/pkg/util/mergepatch/BUILD generated vendored Normal file
View File

@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
importpath = "k8s.io/apimachinery/pkg/util/mergepatch",
library = ":go_default_library",
)
go_library(
name = "go_default_library",
srcs = [
"errors.go",
"util.go",
],
importpath = "k8s.io/apimachinery/pkg/util/mergepatch",
deps = [
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/github.com/ghodss/yaml: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

@ -0,0 +1,5 @@
approvers:
- pwittrock
reviewers:
- mengqiy
- apelisse

View File

@ -0,0 +1,102 @@
/*
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 mergepatch
import (
"errors"
"fmt"
"reflect"
)
var (
ErrBadJSONDoc = errors.New("invalid JSON document")
ErrNoListOfLists = errors.New("lists of lists are not supported")
ErrBadPatchFormatForPrimitiveList = errors.New("invalid patch format of primitive list")
ErrBadPatchFormatForRetainKeys = errors.New("invalid patch format of retainKeys")
ErrBadPatchFormatForSetElementOrderList = errors.New("invalid patch format of setElementOrder list")
ErrPatchContentNotMatchRetainKeys = errors.New("patch content doesn't match retainKeys list")
ErrUnsupportedStrategicMergePatchFormat = errors.New("strategic merge patch format is not supported")
)
func ErrNoMergeKey(m map[string]interface{}, k string) error {
return fmt.Errorf("map: %v does not contain declared merge key: %s", m, k)
}
func ErrBadArgType(expected, actual interface{}) error {
return fmt.Errorf("expected a %s, but received a %s",
reflect.TypeOf(expected),
reflect.TypeOf(actual))
}
func ErrBadArgKind(expected, actual interface{}) error {
var expectedKindString, actualKindString string
if expected == nil {
expectedKindString = "nil"
} else {
expectedKindString = reflect.TypeOf(expected).Kind().String()
}
if actual == nil {
actualKindString = "nil"
} else {
actualKindString = reflect.TypeOf(actual).Kind().String()
}
return fmt.Errorf("expected a %s, but received a %s", expectedKindString, actualKindString)
}
func ErrBadPatchType(t interface{}, m map[string]interface{}) error {
return fmt.Errorf("unknown patch type: %s in map: %v", t, m)
}
// IsPreconditionFailed returns true if the provided error indicates
// a precondition failed.
func IsPreconditionFailed(err error) bool {
_, ok := err.(ErrPreconditionFailed)
return ok
}
type ErrPreconditionFailed struct {
message string
}
func NewErrPreconditionFailed(target map[string]interface{}) ErrPreconditionFailed {
s := fmt.Sprintf("precondition failed for: %v", target)
return ErrPreconditionFailed{s}
}
func (err ErrPreconditionFailed) Error() string {
return err.message
}
type ErrConflict struct {
message string
}
func NewErrConflict(patch, current string) ErrConflict {
s := fmt.Sprintf("patch:\n%s\nconflicts with changes made from original to current:\n%s\n", patch, current)
return ErrConflict{s}
}
func (err ErrConflict) Error() string {
return err.message
}
// IsConflict returns true if the provided error indicates
// a conflict between the patch and the current configuration.
func IsConflict(err error) bool {
_, ok := err.(ErrConflict)
return ok
}

133
vendor/k8s.io/apimachinery/pkg/util/mergepatch/util.go generated vendored Normal file
View File

@ -0,0 +1,133 @@
/*
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 mergepatch
import (
"fmt"
"reflect"
"github.com/davecgh/go-spew/spew"
"github.com/ghodss/yaml"
)
// PreconditionFunc asserts that an incompatible change is not present within a patch.
type PreconditionFunc func(interface{}) bool
// RequireKeyUnchanged returns a precondition function that fails if the provided key
// is present in the patch (indicating that its value has changed).
func RequireKeyUnchanged(key string) PreconditionFunc {
return func(patch interface{}) bool {
patchMap, ok := patch.(map[string]interface{})
if !ok {
return true
}
// The presence of key means that its value has been changed, so the test fails.
_, ok = patchMap[key]
return !ok
}
}
// RequireMetadataKeyUnchanged creates a precondition function that fails
// if the metadata.key is present in the patch (indicating its value
// has changed).
func RequireMetadataKeyUnchanged(key string) PreconditionFunc {
return func(patch interface{}) bool {
patchMap, ok := patch.(map[string]interface{})
if !ok {
return true
}
patchMap1, ok := patchMap["metadata"]
if !ok {
return true
}
patchMap2, ok := patchMap1.(map[string]interface{})
if !ok {
return true
}
_, ok = patchMap2[key]
return !ok
}
}
func ToYAMLOrError(v interface{}) string {
y, err := toYAML(v)
if err != nil {
return err.Error()
}
return y
}
func toYAML(v interface{}) (string, error) {
y, err := yaml.Marshal(v)
if err != nil {
return "", fmt.Errorf("yaml marshal failed:%v\n%v\n", err, spew.Sdump(v))
}
return string(y), nil
}
// HasConflicts returns true if the left and right JSON interface objects overlap with
// different values in any key. All keys are required to be strings. Since patches of the
// same Type have congruent keys, this is valid for multiple patch types. This method
// supports JSON merge patch semantics.
//
// NOTE: Numbers with different types (e.g. int(0) vs int64(0)) will be detected as conflicts.
// Make sure the unmarshaling of left and right are consistent (e.g. use the same library).
func HasConflicts(left, right interface{}) (bool, error) {
switch typedLeft := left.(type) {
case map[string]interface{}:
switch typedRight := right.(type) {
case map[string]interface{}:
for key, leftValue := range typedLeft {
rightValue, ok := typedRight[key]
if !ok {
continue
}
if conflict, err := HasConflicts(leftValue, rightValue); err != nil || conflict {
return conflict, err
}
}
return false, nil
default:
return true, nil
}
case []interface{}:
switch typedRight := right.(type) {
case []interface{}:
if len(typedLeft) != len(typedRight) {
return true, nil
}
for i := range typedLeft {
if conflict, err := HasConflicts(typedLeft[i], typedRight[i]); err != nil || conflict {
return conflict, err
}
}
return false, nil
default:
return true, nil
}
case string, float64, bool, int, int64, nil:
return !reflect.DeepEqual(left, right), nil
default:
return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left))
}
}

View File

@ -0,0 +1,138 @@
/*
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 mergepatch
import (
"fmt"
"testing"
)
func TestHasConflicts(t *testing.T) {
testCases := []struct {
A interface{}
B interface{}
Ret bool
}{
{A: "hello", B: "hello", Ret: false},
{A: "hello", B: "hell", Ret: true},
{A: "hello", B: nil, Ret: true},
{A: "hello", B: 1, Ret: true},
{A: "hello", B: float64(1.0), Ret: true},
{A: "hello", B: false, Ret: true},
{A: 1, B: 1, Ret: false},
{A: nil, B: nil, Ret: false},
{A: false, B: false, Ret: false},
{A: float64(3), B: float64(3), Ret: false},
{A: "hello", B: []interface{}{}, Ret: true},
{A: []interface{}{1}, B: []interface{}{}, Ret: true},
{A: []interface{}{}, B: []interface{}{}, Ret: false},
{A: []interface{}{1}, B: []interface{}{1}, Ret: false},
{A: map[string]interface{}{}, B: []interface{}{1}, Ret: true},
{A: map[string]interface{}{}, B: map[string]interface{}{"a": 1}, Ret: false},
{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"a": 1}, Ret: false},
{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"a": 2}, Ret: true},
{A: map[string]interface{}{"a": 1}, B: map[string]interface{}{"b": 2}, Ret: false},
{
A: map[string]interface{}{"a": []interface{}{1}},
B: map[string]interface{}{"a": []interface{}{1}},
Ret: false,
},
{
A: map[string]interface{}{"a": []interface{}{1}},
B: map[string]interface{}{"a": []interface{}{}},
Ret: true,
},
{
A: map[string]interface{}{"a": []interface{}{1}},
B: map[string]interface{}{"a": 1},
Ret: true,
},
// Maps and lists with multiple entries.
{
A: map[string]interface{}{"a": 1, "b": 2},
B: map[string]interface{}{"a": 1, "b": 0},
Ret: true,
},
{
A: map[string]interface{}{"a": 1, "b": 2},
B: map[string]interface{}{"a": 1, "b": 2},
Ret: false,
},
{
A: map[string]interface{}{"a": 1, "b": 2},
B: map[string]interface{}{"a": 1, "b": 0, "c": 3},
Ret: true,
},
{
A: map[string]interface{}{"a": 1, "b": 2},
B: map[string]interface{}{"a": 1, "b": 2, "c": 3},
Ret: false,
},
{
A: map[string]interface{}{"a": []interface{}{1, 2}},
B: map[string]interface{}{"a": []interface{}{1, 0}},
Ret: true,
},
{
A: map[string]interface{}{"a": []interface{}{1, 2}},
B: map[string]interface{}{"a": []interface{}{1, 2}},
Ret: false,
},
// Numeric types are not interchangeable.
// Callers are expected to ensure numeric types are consistent in 'left' and 'right'.
{A: int(0), B: int64(0), Ret: true},
{A: int(0), B: float64(0), Ret: true},
{A: int64(0), B: float64(0), Ret: true},
// Other types are not interchangeable.
{A: int(0), B: "0", Ret: true},
{A: int(0), B: nil, Ret: true},
{A: int(0), B: false, Ret: true},
{A: "true", B: true, Ret: true},
{A: "null", B: nil, Ret: true},
}
for _, testCase := range testCases {
testStr := fmt.Sprintf("A = %#v, B = %#v", testCase.A, testCase.B)
// Run each test case multiple times if it passes because HasConflicts()
// uses map iteration, which returns keys in nondeterministic order.
for try := 0; try < 10; try++ {
out, err := HasConflicts(testCase.A, testCase.B)
if err != nil {
t.Errorf("%v: unexpected error: %v", testStr, err)
break
}
if out != testCase.Ret {
t.Errorf("%v: expected %t got %t", testStr, testCase.Ret, out)
break
}
out, err = HasConflicts(testCase.B, testCase.A)
if err != nil {
t.Errorf("%v: unexpected error: %v", testStr, err)
break
}
if out != testCase.Ret {
t.Errorf("%v: expected reversed %t got %t", testStr, testCase.Ret, out)
break
}
}
}
}

51
vendor/k8s.io/apimachinery/pkg/util/net/BUILD generated vendored Normal file
View File

@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"http_test.go",
"interface_test.go",
"port_range_test.go",
"port_split_test.go",
"util_test.go",
],
importpath = "k8s.io/apimachinery/pkg/util/net",
library = ":go_default_library",
deps = ["//vendor/github.com/spf13/pflag:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"http.go",
"interface.go",
"port_range.go",
"port_split.go",
"util.go",
],
importpath = "k8s.io/apimachinery/pkg/util/net",
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/net/http2:go_default_library",
"//vendor/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"],
)

422
vendor/k8s.io/apimachinery/pkg/util/net/http.go generated vendored Normal file
View File

@ -0,0 +1,422 @@
/*
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 net
import (
"bufio"
"bytes"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"github.com/golang/glog"
"golang.org/x/net/http2"
)
// JoinPreservingTrailingSlash does a path.Join of the specified elements,
// preserving any trailing slash on the last non-empty segment
func JoinPreservingTrailingSlash(elem ...string) string {
// do the basic path join
result := path.Join(elem...)
// find the last non-empty segment
for i := len(elem) - 1; i >= 0; i-- {
if len(elem[i]) > 0 {
// if the last segment ended in a slash, ensure our result does as well
if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") {
result += "/"
}
break
}
}
return result
}
// IsProbableEOF returns true if the given error resembles a connection termination
// scenario that would justify assuming that the watch is empty.
// These errors are what the Go http stack returns back to us which are general
// connection closure errors (strongly correlated) and callers that need to
// differentiate probable errors in connection behavior between normal "this is
// disconnected" should use the method.
func IsProbableEOF(err error) bool {
if uerr, ok := err.(*url.Error); ok {
err = uerr.Err
}
switch {
case err == io.EOF:
return true
case err.Error() == "http: can't write HTTP request on broken connection":
return true
case strings.Contains(err.Error(), "connection reset by peer"):
return true
case strings.Contains(strings.ToLower(err.Error()), "use of closed network connection"):
return true
}
return false
}
var defaultTransport = http.DefaultTransport.(*http.Transport)
// SetOldTransportDefaults applies the defaults from http.DefaultTransport
// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
func SetOldTransportDefaults(t *http.Transport) *http.Transport {
if t.Proxy == nil || isDefault(t.Proxy) {
// http.ProxyFromEnvironment doesn't respect CIDRs and that makes it impossible to exclude things like pod and service IPs from proxy settings
// ProxierWithNoProxyCIDR allows CIDR rules in NO_PROXY
t.Proxy = NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
}
if t.Dial == nil {
t.Dial = defaultTransport.Dial
}
if t.TLSHandshakeTimeout == 0 {
t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout
}
return t
}
// SetTransportDefaults applies the defaults from http.DefaultTransport
// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
func SetTransportDefaults(t *http.Transport) *http.Transport {
t = SetOldTransportDefaults(t)
// Allow clients to disable http2 if needed.
if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {
glog.Infof("HTTP2 has been explicitly disabled")
} else {
if err := http2.ConfigureTransport(t); err != nil {
glog.Warningf("Transport failed http2 configuration: %v", err)
}
}
return t
}
type RoundTripperWrapper interface {
http.RoundTripper
WrappedRoundTripper() http.RoundTripper
}
type DialFunc func(net, addr string) (net.Conn, error)
func DialerFor(transport http.RoundTripper) (DialFunc, error) {
if transport == nil {
return nil, nil
}
switch transport := transport.(type) {
case *http.Transport:
return transport.Dial, nil
case RoundTripperWrapper:
return DialerFor(transport.WrappedRoundTripper())
default:
return nil, fmt.Errorf("unknown transport type: %T", transport)
}
}
type TLSClientConfigHolder interface {
TLSClientConfig() *tls.Config
}
func TLSClientConfig(transport http.RoundTripper) (*tls.Config, error) {
if transport == nil {
return nil, nil
}
switch transport := transport.(type) {
case *http.Transport:
return transport.TLSClientConfig, nil
case TLSClientConfigHolder:
return transport.TLSClientConfig(), nil
case RoundTripperWrapper:
return TLSClientConfig(transport.WrappedRoundTripper())
default:
return nil, fmt.Errorf("unknown transport type: %T", transport)
}
}
func FormatURL(scheme string, host string, port int, path string) *url.URL {
return &url.URL{
Scheme: scheme,
Host: net.JoinHostPort(host, strconv.Itoa(port)),
Path: path,
}
}
func GetHTTPClient(req *http.Request) string {
if userAgent, ok := req.Header["User-Agent"]; ok {
if len(userAgent) > 0 {
return userAgent[0]
}
}
return "unknown"
}
// SourceIPs splits the comma separated X-Forwarded-For header or returns the X-Real-Ip header or req.RemoteAddr,
// in that order, ignoring invalid IPs. It returns nil if all of these are empty or invalid.
func SourceIPs(req *http.Request) []net.IP {
hdr := req.Header
// First check the X-Forwarded-For header for requests via proxy.
hdrForwardedFor := hdr.Get("X-Forwarded-For")
forwardedForIPs := []net.IP{}
if hdrForwardedFor != "" {
// X-Forwarded-For can be a csv of IPs in case of multiple proxies.
// Use the first valid one.
parts := strings.Split(hdrForwardedFor, ",")
for _, part := range parts {
ip := net.ParseIP(strings.TrimSpace(part))
if ip != nil {
forwardedForIPs = append(forwardedForIPs, ip)
}
}
}
if len(forwardedForIPs) > 0 {
return forwardedForIPs
}
// Try the X-Real-Ip header.
hdrRealIp := hdr.Get("X-Real-Ip")
if hdrRealIp != "" {
ip := net.ParseIP(hdrRealIp)
if ip != nil {
return []net.IP{ip}
}
}
// Fallback to Remote Address in request, which will give the correct client IP when there is no proxy.
// Remote Address in Go's HTTP server is in the form host:port so we need to split that first.
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err == nil {
if remoteIP := net.ParseIP(host); remoteIP != nil {
return []net.IP{remoteIP}
}
}
// Fallback if Remote Address was just IP.
if remoteIP := net.ParseIP(req.RemoteAddr); remoteIP != nil {
return []net.IP{remoteIP}
}
return nil
}
// Extracts and returns the clients IP from the given request.
// Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order.
// Returns nil if none of them are set or is set to an invalid value.
func GetClientIP(req *http.Request) net.IP {
ips := SourceIPs(req)
if len(ips) == 0 {
return nil
}
return ips[0]
}
// Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's
// IP address to the X-Forwarded-For chain.
func AppendForwardedForHeader(req *http.Request) {
// Copied from net/http/httputil/reverseproxy.go:
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}
}
var defaultProxyFuncPointer = fmt.Sprintf("%p", http.ProxyFromEnvironment)
// isDefault checks to see if the transportProxierFunc is pointing to the default one
func isDefault(transportProxier func(*http.Request) (*url.URL, error)) bool {
transportProxierPointer := fmt.Sprintf("%p", transportProxier)
return transportProxierPointer == defaultProxyFuncPointer
}
// NewProxierWithNoProxyCIDR constructs a Proxier function that respects CIDRs in NO_PROXY and delegates if
// no matching CIDRs are found
func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) {
// we wrap the default method, so we only need to perform our check if the NO_PROXY (or no_proxy) envvar has a CIDR in it
noProxyEnv := os.Getenv("NO_PROXY")
if noProxyEnv == "" {
noProxyEnv = os.Getenv("no_proxy")
}
noProxyRules := strings.Split(noProxyEnv, ",")
cidrs := []*net.IPNet{}
for _, noProxyRule := range noProxyRules {
_, cidr, _ := net.ParseCIDR(noProxyRule)
if cidr != nil {
cidrs = append(cidrs, cidr)
}
}
if len(cidrs) == 0 {
return delegate
}
return func(req *http.Request) (*url.URL, error) {
ip := net.ParseIP(req.URL.Hostname())
if ip == nil {
return delegate(req)
}
for _, cidr := range cidrs {
if cidr.Contains(ip) {
return nil, nil
}
}
return delegate(req)
}
}
// DialerFunc implements Dialer for the provided function.
type DialerFunc func(req *http.Request) (net.Conn, error)
func (fn DialerFunc) Dial(req *http.Request) (net.Conn, error) {
return fn(req)
}
// Dialer dials a host and writes a request to it.
type Dialer interface {
// Dial connects to the host specified by req's URL, writes the request to the connection, and
// returns the opened net.Conn.
Dial(req *http.Request) (net.Conn, error)
}
// ConnectWithRedirects uses dialer to send req, following up to 10 redirects (relative to
// originalLocation). It returns the opened net.Conn and the raw response bytes.
func ConnectWithRedirects(originalMethod string, originalLocation *url.URL, header http.Header, originalBody io.Reader, dialer Dialer) (net.Conn, []byte, error) {
const (
maxRedirects = 10
maxResponseSize = 16384 // play it safe to allow the potential for lots of / large headers
)
var (
location = originalLocation
method = originalMethod
intermediateConn net.Conn
rawResponse = bytes.NewBuffer(make([]byte, 0, 256))
body = originalBody
)
defer func() {
if intermediateConn != nil {
intermediateConn.Close()
}
}()
redirectLoop:
for redirects := 0; ; redirects++ {
if redirects > maxRedirects {
return nil, nil, fmt.Errorf("too many redirects (%d)", redirects)
}
req, err := http.NewRequest(method, location.String(), body)
if err != nil {
return nil, nil, err
}
req.Header = header
intermediateConn, err = dialer.Dial(req)
if err != nil {
return nil, nil, err
}
// Peek at the backend response.
rawResponse.Reset()
respReader := bufio.NewReader(io.TeeReader(
io.LimitReader(intermediateConn, maxResponseSize), // Don't read more than maxResponseSize bytes.
rawResponse)) // Save the raw response.
resp, err := http.ReadResponse(respReader, nil)
if err != nil {
// Unable to read the backend response; let the client handle it.
glog.Warningf("Error reading backend response: %v", err)
break redirectLoop
}
switch resp.StatusCode {
case http.StatusFound:
// Redirect, continue.
default:
// Don't redirect.
break redirectLoop
}
// Redirected requests switch to "GET" according to the HTTP spec:
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3
method = "GET"
// don't send a body when following redirects
body = nil
resp.Body.Close() // not used
// Reset the connection.
intermediateConn.Close()
intermediateConn = nil
// Prepare to follow the redirect.
redirectStr := resp.Header.Get("Location")
if redirectStr == "" {
return nil, nil, fmt.Errorf("%d response missing Location header", resp.StatusCode)
}
// We have to parse relative to the current location, NOT originalLocation. For example,
// if we request http://foo.com/a and get back "http://bar.com/b", the result should be
// http://bar.com/b. If we then make that request and get back a redirect to "/c", the result
// should be http://bar.com/c, not http://foo.com/c.
location, err = location.Parse(redirectStr)
if err != nil {
return nil, nil, fmt.Errorf("malformed Location header: %v", err)
}
}
connToReturn := intermediateConn
intermediateConn = nil // Don't close the connection when we return it.
return connToReturn, rawResponse.Bytes(), nil
}
// CloneRequest creates a shallow copy of the request along with a deep copy of the Headers.
func CloneRequest(req *http.Request) *http.Request {
r := new(http.Request)
// shallow clone
*r = *req
// deep copy headers
r.Header = CloneHeader(req.Header)
return r
}
// CloneHeader creates a deep copy of an http.Header.
func CloneHeader(in http.Header) http.Header {
out := make(http.Header, len(in))
for key, values := range in {
newValues := make([]string, len(values))
copy(newValues, values)
out[key] = newValues
}
return out
}

282
vendor/k8s.io/apimachinery/pkg/util/net/http_test.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
// +build go1.8
/*
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 net
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"os"
"reflect"
"testing"
)
func TestGetClientIP(t *testing.T) {
ipString := "10.0.0.1"
ip := net.ParseIP(ipString)
invalidIPString := "invalidIPString"
testCases := []struct {
Request http.Request
ExpectedIP net.IP
}{
{
Request: http.Request{},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Real-Ip": {ipString},
},
},
ExpectedIP: ip,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Real-Ip": {invalidIPString},
},
},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {ipString},
},
},
ExpectedIP: ip,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {invalidIPString},
},
},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {invalidIPString + "," + ipString},
},
},
ExpectedIP: ip,
},
{
Request: http.Request{
// RemoteAddr is in the form host:port
RemoteAddr: ipString + ":1234",
},
ExpectedIP: ip,
},
{
Request: http.Request{
RemoteAddr: invalidIPString,
},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {invalidIPString},
},
// RemoteAddr is in the form host:port
RemoteAddr: ipString,
},
ExpectedIP: ip,
},
}
for i, test := range testCases {
if a, e := GetClientIP(&test.Request), test.ExpectedIP; reflect.DeepEqual(e, a) != true {
t.Fatalf("test case %d failed. expected: %v, actual: %v", i, e, a)
}
}
}
func TestAppendForwardedForHeader(t *testing.T) {
testCases := []struct {
addr, forwarded, expected string
}{
{"1.2.3.4:8000", "", "1.2.3.4"},
{"1.2.3.4:8000", "8.8.8.8", "8.8.8.8, 1.2.3.4"},
{"1.2.3.4:8000", "8.8.8.8, 1.2.3.4", "8.8.8.8, 1.2.3.4, 1.2.3.4"},
{"1.2.3.4:8000", "foo,bar", "foo,bar, 1.2.3.4"},
}
for i, test := range testCases {
req := &http.Request{
RemoteAddr: test.addr,
Header: make(http.Header),
}
if test.forwarded != "" {
req.Header.Set("X-Forwarded-For", test.forwarded)
}
AppendForwardedForHeader(req)
actual := req.Header.Get("X-Forwarded-For")
if actual != test.expected {
t.Errorf("[%d] Expected %q, Got %q", i, test.expected, actual)
}
}
}
func TestProxierWithNoProxyCIDR(t *testing.T) {
testCases := []struct {
name string
noProxy string
url string
expectedDelegated bool
}{
{
name: "no env",
url: "https://192.168.143.1/api",
expectedDelegated: true,
},
{
name: "no cidr",
noProxy: "192.168.63.1",
url: "https://192.168.143.1/api",
expectedDelegated: true,
},
{
name: "hostname",
noProxy: "192.168.63.0/24,192.168.143.0/24",
url: "https://my-hostname/api",
expectedDelegated: true,
},
{
name: "match second cidr",
noProxy: "192.168.63.0/24,192.168.143.0/24",
url: "https://192.168.143.1/api",
expectedDelegated: false,
},
{
name: "match second cidr with host:port",
noProxy: "192.168.63.0/24,192.168.143.0/24",
url: "https://192.168.143.1:8443/api",
expectedDelegated: false,
},
{
name: "IPv6 cidr",
noProxy: "2001:db8::/48",
url: "https://[2001:db8::1]/api",
expectedDelegated: false,
},
{
name: "IPv6+port cidr",
noProxy: "2001:db8::/48",
url: "https://[2001:db8::1]:8443/api",
expectedDelegated: false,
},
{
name: "IPv6, not matching cidr",
noProxy: "2001:db8::/48",
url: "https://[2001:db8:1::1]/api",
expectedDelegated: true,
},
{
name: "IPv6+port, not matching cidr",
noProxy: "2001:db8::/48",
url: "https://[2001:db8:1::1]:8443/api",
expectedDelegated: true,
},
}
for _, test := range testCases {
os.Setenv("NO_PROXY", test.noProxy)
actualDelegated := false
proxyFunc := NewProxierWithNoProxyCIDR(func(req *http.Request) (*url.URL, error) {
actualDelegated = true
return nil, nil
})
req, err := http.NewRequest("GET", test.url, nil)
if err != nil {
t.Errorf("%s: unexpected err: %v", test.name, err)
continue
}
if _, err := proxyFunc(req); err != nil {
t.Errorf("%s: unexpected err: %v", test.name, err)
continue
}
if test.expectedDelegated != actualDelegated {
t.Errorf("%s: expected %v, got %v", test.name, test.expectedDelegated, actualDelegated)
continue
}
}
}
type fakeTLSClientConfigHolder struct {
called bool
}
func (f *fakeTLSClientConfigHolder) TLSClientConfig() *tls.Config {
f.called = true
return nil
}
func (f *fakeTLSClientConfigHolder) RoundTrip(*http.Request) (*http.Response, error) {
return nil, nil
}
func TestTLSClientConfigHolder(t *testing.T) {
rt := &fakeTLSClientConfigHolder{}
TLSClientConfig(rt)
if !rt.called {
t.Errorf("didn't find tls config")
}
}
func TestJoinPreservingTrailingSlash(t *testing.T) {
tests := []struct {
a string
b string
want string
}{
// All empty
{"", "", ""},
// Empty a
{"", "/", "/"},
{"", "foo", "foo"},
{"", "/foo", "/foo"},
{"", "/foo/", "/foo/"},
// Empty b
{"/", "", "/"},
{"foo", "", "foo"},
{"/foo", "", "/foo"},
{"/foo/", "", "/foo/"},
// Both populated
{"/", "/", "/"},
{"foo", "foo", "foo/foo"},
{"/foo", "/foo", "/foo/foo"},
{"/foo/", "/foo/", "/foo/foo/"},
}
for _, tt := range tests {
name := fmt.Sprintf("%q+%q=%q", tt.a, tt.b, tt.want)
t.Run(name, func(t *testing.T) {
if got := JoinPreservingTrailingSlash(tt.a, tt.b); got != tt.want {
t.Errorf("JoinPreservingTrailingSlash() = %v, want %v", got, tt.want)
}
})
}
}

392
vendor/k8s.io/apimachinery/pkg/util/net/interface.go generated vendored Normal file
View File

@ -0,0 +1,392 @@
/*
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 net
import (
"bufio"
"encoding/hex"
"fmt"
"io"
"net"
"os"
"strings"
"github.com/golang/glog"
)
type AddressFamily uint
const (
familyIPv4 AddressFamily = 4
familyIPv6 AddressFamily = 6
)
const (
ipv4RouteFile = "/proc/net/route"
ipv6RouteFile = "/proc/net/ipv6_route"
)
type Route struct {
Interface string
Destination net.IP
Gateway net.IP
Family AddressFamily
}
type RouteFile struct {
name string
parse func(input io.Reader) ([]Route, error)
}
var (
v4File = RouteFile{name: ipv4RouteFile, parse: getIPv4DefaultRoutes}
v6File = RouteFile{name: ipv6RouteFile, parse: getIPv6DefaultRoutes}
)
func (rf RouteFile) extract() ([]Route, error) {
file, err := os.Open(rf.name)
if err != nil {
return nil, err
}
defer file.Close()
return rf.parse(file)
}
// getIPv4DefaultRoutes obtains the IPv4 routes, and filters out non-default routes.
func getIPv4DefaultRoutes(input io.Reader) ([]Route, error) {
routes := []Route{}
scanner := bufio.NewReader(input)
for {
line, err := scanner.ReadString('\n')
if err == io.EOF {
break
}
//ignore the headers in the route info
if strings.HasPrefix(line, "Iface") {
continue
}
fields := strings.Fields(line)
// Interested in fields:
// 0 - interface name
// 1 - destination address
// 2 - gateway
dest, err := parseIP(fields[1], familyIPv4)
if err != nil {
return nil, err
}
gw, err := parseIP(fields[2], familyIPv4)
if err != nil {
return nil, err
}
if !dest.Equal(net.IPv4zero) {
continue
}
routes = append(routes, Route{
Interface: fields[0],
Destination: dest,
Gateway: gw,
Family: familyIPv4,
})
}
return routes, nil
}
func getIPv6DefaultRoutes(input io.Reader) ([]Route, error) {
routes := []Route{}
scanner := bufio.NewReader(input)
for {
line, err := scanner.ReadString('\n')
if err == io.EOF {
break
}
fields := strings.Fields(line)
// Interested in fields:
// 0 - destination address
// 4 - gateway
// 9 - interface name
dest, err := parseIP(fields[0], familyIPv6)
if err != nil {
return nil, err
}
gw, err := parseIP(fields[4], familyIPv6)
if err != nil {
return nil, err
}
if !dest.Equal(net.IPv6zero) {
continue
}
if gw.Equal(net.IPv6zero) {
continue // loopback
}
routes = append(routes, Route{
Interface: fields[9],
Destination: dest,
Gateway: gw,
Family: familyIPv6,
})
}
return routes, nil
}
// parseIP takes the hex IP address string from route file and converts it
// to a net.IP address. For IPv4, the value must be converted to big endian.
func parseIP(str string, family AddressFamily) (net.IP, error) {
if str == "" {
return nil, fmt.Errorf("input is nil")
}
bytes, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
if family == familyIPv4 {
if len(bytes) != net.IPv4len {
return nil, fmt.Errorf("invalid IPv4 address in route")
}
return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil
}
// Must be IPv6
if len(bytes) != net.IPv6len {
return nil, fmt.Errorf("invalid IPv6 address in route")
}
return net.IP(bytes), nil
}
func isInterfaceUp(intf *net.Interface) bool {
if intf == nil {
return false
}
if intf.Flags&net.FlagUp != 0 {
glog.V(4).Infof("Interface %v is up", intf.Name)
return true
}
return false
}
func isLoopbackOrPointToPoint(intf *net.Interface) bool {
return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0
}
// getMatchingGlobalIP returns the first valid global unicast address of the given
// 'family' from the list of 'addrs'.
func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) {
if len(addrs) > 0 {
for i := range addrs {
glog.V(4).Infof("Checking addr %s.", addrs[i].String())
ip, _, err := net.ParseCIDR(addrs[i].String())
if err != nil {
return nil, err
}
if memberOf(ip, family) {
if ip.IsGlobalUnicast() {
glog.V(4).Infof("IP found %v", ip)
return ip, nil
} else {
glog.V(4).Infof("Non-global unicast address found %v", ip)
}
} else {
glog.V(4).Infof("%v is not an IPv%d address", ip, int(family))
}
}
}
return nil, nil
}
// getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The
// interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
intf, err := nw.InterfaceByName(intfName)
if err != nil {
return nil, err
}
if isInterfaceUp(intf) {
addrs, err := nw.Addrs(intf)
if err != nil {
return nil, err
}
glog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs)
matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
if err != nil {
return nil, err
}
if matchingIP != nil {
glog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName)
return matchingIP, nil
}
}
return nil, nil
}
// memberOF tells if the IP is of the desired family. Used for checking interface addresses.
func memberOf(ip net.IP, family AddressFamily) bool {
if ip.To4() != nil {
return family == familyIPv4
} else {
return family == familyIPv6
}
}
// chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that
// has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP.
// Searches for IPv4 addresses, and then IPv6 addresses.
func chooseIPFromHostInterfaces(nw networkInterfacer) (net.IP, error) {
intfs, err := nw.Interfaces()
if err != nil {
return nil, err
}
if len(intfs) == 0 {
return nil, fmt.Errorf("no interfaces found on host.")
}
for _, family := range []AddressFamily{familyIPv4, familyIPv6} {
glog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family))
for _, intf := range intfs {
if !isInterfaceUp(&intf) {
glog.V(4).Infof("Skipping: down interface %q", intf.Name)
continue
}
if isLoopbackOrPointToPoint(&intf) {
glog.V(4).Infof("Skipping: LB or P2P interface %q", intf.Name)
continue
}
addrs, err := nw.Addrs(&intf)
if err != nil {
return nil, err
}
if len(addrs) == 0 {
glog.V(4).Infof("Skipping: no addresses on interface %q", intf.Name)
continue
}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
return nil, fmt.Errorf("Unable to parse CIDR for interface %q: %s", intf.Name, err)
}
if !memberOf(ip, family) {
glog.V(4).Infof("Skipping: no address family match for %q on interface %q.", ip, intf.Name)
continue
}
// TODO: Decide if should open up to allow IPv6 LLAs in future.
if !ip.IsGlobalUnicast() {
glog.V(4).Infof("Skipping: non-global address %q on interface %q.", ip, intf.Name)
continue
}
glog.V(4).Infof("Found global unicast address %q on interface %q.", ip, intf.Name)
return ip, nil
}
}
}
return nil, fmt.Errorf("no acceptable interface with global unicast address found on host")
}
// ChooseHostInterface is a method used fetch an IP for a daemon.
// If there is no routing info file, it will choose a global IP from the system
// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the
// IP of the interface with a gateway on it (with priority given to IPv4). For a node
// with no internet connection, it returns error.
func ChooseHostInterface() (net.IP, error) {
var nw networkInterfacer = networkInterface{}
if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) {
return chooseIPFromHostInterfaces(nw)
}
routes, err := getAllDefaultRoutes()
if err != nil {
return nil, err
}
return chooseHostInterfaceFromRoute(routes, nw)
}
// networkInterfacer defines an interface for several net library functions. Production
// code will forward to net library functions, and unit tests will override the methods
// for testing purposes.
type networkInterfacer interface {
InterfaceByName(intfName string) (*net.Interface, error)
Addrs(intf *net.Interface) ([]net.Addr, error)
Interfaces() ([]net.Interface, error)
}
// networkInterface implements the networkInterfacer interface for production code, just
// wrapping the underlying net library function calls.
type networkInterface struct{}
func (_ networkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return net.InterfaceByName(intfName)
}
func (_ networkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
return intf.Addrs()
}
func (_ networkInterface) Interfaces() ([]net.Interface, error) {
return net.Interfaces()
}
// getAllDefaultRoutes obtains IPv4 and IPv6 default routes on the node. If unable
// to read the IPv4 routing info file, we return an error. If unable to read the IPv6
// routing info file (which is optional), we'll just use the IPv4 route information.
// Using all the routing info, if no default routes are found, an error is returned.
func getAllDefaultRoutes() ([]Route, error) {
routes, err := v4File.extract()
if err != nil {
return nil, err
}
v6Routes, _ := v6File.extract()
routes = append(routes, v6Routes...)
if len(routes) == 0 {
return nil, fmt.Errorf("No default routes.")
}
return routes, nil
}
// chooseHostInterfaceFromRoute cycles through each default route provided, looking for a
// global IP address from the interface for the route. Will first look all each IPv4 route for
// an IPv4 IP, and then will look at each IPv6 route for an IPv6 IP.
func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer) (net.IP, error) {
for _, family := range []AddressFamily{familyIPv4, familyIPv6} {
glog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family))
for _, route := range routes {
if route.Family != family {
continue
}
glog.V(4).Infof("Default route transits interface %q", route.Interface)
finalIP, err := getIPFromInterface(route.Interface, family, nw)
if err != nil {
return nil, err
}
if finalIP != nil {
glog.V(4).Infof("Found active IP %v ", finalIP)
return finalIP, nil
}
}
}
glog.V(4).Infof("No active IP found by looking at default routes")
return nil, fmt.Errorf("unable to select an IP from default routes.")
}
// If bind-address is usable, return it directly
// If bind-address is not usable (unset, 0.0.0.0, or loopback), we will use the host's default
// interface.
func ChooseBindAddress(bindAddress net.IP) (net.IP, error) {
if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() {
hostIP, err := ChooseHostInterface()
if err != nil {
return nil, err
}
bindAddress = hostIP
}
return bindAddress, nil
}

View File

@ -0,0 +1,725 @@
/*
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 net
import (
"fmt"
"io/ioutil"
"net"
"os"
"strings"
"testing"
)
const gatewayfirst = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0
eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
`
const gatewaylast = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0
eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0
`
const gatewaymiddle = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
`
const noInternetConnection = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
`
const nothing = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
`
const badDestination = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0
eth3 0000FE0AA1 00000000 0001 0 0 0 0080FFFF 0 0 0
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
`
const badGateway = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth3 00000000 0100FE0AA1 0003 0 0 1024 00000000 0 0 0
eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
`
const route_Invalidhex = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth3 00000000 0100FE0AA 0003 0 0 1024 00000000 0 0 0
eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
`
const v6gatewayfirst = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3
20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
`
const v6gatewaylast = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3
`
const v6gatewaymiddle = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3
00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
`
const v6noDefaultRoutes = `00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
20010001000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00000001 docker0
20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
fe800000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
`
const v6nothing = ``
const v6badDestination = `2001000200000000 7a 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
`
const v6badGateway = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 200100010000000000000000000000000012 00000064 00000000 00000000 00000003 eth3
`
const v6route_Invalidhex = `000000000000000000000000000000000 00 00000000000000000000000000000000 00 fe80000000000000021fcafffea0ec00 00000064 00000000 00000000 00000003 enp1s0f0
`
const (
flagUp = net.FlagUp | net.FlagBroadcast | net.FlagMulticast
flagDown = net.FlagBroadcast | net.FlagMulticast
flagLoopback = net.FlagUp | net.FlagLoopback
flagP2P = net.FlagUp | net.FlagPointToPoint
)
func makeIntf(index int, name string, flags net.Flags) net.Interface {
mac := net.HardwareAddr{0, 0x32, 0x7d, 0x69, 0xf7, byte(0x30 + index)}
return net.Interface{
Index: index,
MTU: 1500,
Name: name,
HardwareAddr: mac,
Flags: flags}
}
var (
downIntf = makeIntf(1, "eth3", flagDown)
loopbackIntf = makeIntf(1, "lo", flagLoopback)
p2pIntf = makeIntf(1, "lo", flagP2P)
upIntf = makeIntf(1, "eth3", flagUp)
)
var (
ipv4Route = Route{Interface: "eth3", Destination: net.ParseIP("0.0.0.0"), Gateway: net.ParseIP("10.254.0.1"), Family: familyIPv4}
ipv6Route = Route{Interface: "eth3", Destination: net.ParseIP("::"), Gateway: net.ParseIP("2001:1::1"), Family: familyIPv6}
)
var (
noRoutes = []Route{}
routeV4 = []Route{ipv4Route}
routeV6 = []Route{ipv6Route}
bothRoutes = []Route{ipv4Route, ipv6Route}
)
func TestGetIPv4Routes(t *testing.T) {
testCases := []struct {
tcase string
route string
count int
expected *Route
errStrFrag string
}{
{"gatewayfirst", gatewayfirst, 1, &ipv4Route, ""},
{"gatewaymiddle", gatewaymiddle, 1, &ipv4Route, ""},
{"gatewaylast", gatewaylast, 1, &ipv4Route, ""},
{"no routes", nothing, 0, nil, ""},
{"badDestination", badDestination, 0, nil, "invalid IPv4"},
{"badGateway", badGateway, 0, nil, "invalid IPv4"},
{"route_Invalidhex", route_Invalidhex, 0, nil, "odd length hex string"},
{"no default routes", noInternetConnection, 0, nil, ""},
}
for _, tc := range testCases {
r := strings.NewReader(tc.route)
routes, err := getIPv4DefaultRoutes(r)
if err != nil {
if !strings.Contains(err.Error(), tc.errStrFrag) {
t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag)
}
} else if tc.errStrFrag != "" {
t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag)
} else {
if tc.count != len(routes) {
t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes)
} else if tc.count == 1 {
if !tc.expected.Gateway.Equal(routes[0].Gateway) {
t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err)
}
if !routes[0].Destination.Equal(net.IPv4zero) {
t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase)
}
}
}
}
}
func TestGetIPv6Routes(t *testing.T) {
testCases := []struct {
tcase string
route string
count int
expected *Route
errStrFrag string
}{
{"v6 gatewayfirst", v6gatewayfirst, 1, &ipv6Route, ""},
{"v6 gatewaymiddle", v6gatewaymiddle, 1, &ipv6Route, ""},
{"v6 gatewaylast", v6gatewaylast, 1, &ipv6Route, ""},
{"v6 no routes", v6nothing, 0, nil, ""},
{"v6 badDestination", v6badDestination, 0, nil, "invalid IPv6"},
{"v6 badGateway", v6badGateway, 0, nil, "invalid IPv6"},
{"v6 route_Invalidhex", v6route_Invalidhex, 0, nil, "odd length hex string"},
{"v6 no default routes", v6noDefaultRoutes, 0, nil, ""},
}
for _, tc := range testCases {
r := strings.NewReader(tc.route)
routes, err := getIPv6DefaultRoutes(r)
if err != nil {
if !strings.Contains(err.Error(), tc.errStrFrag) {
t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag)
}
} else if tc.errStrFrag != "" {
t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag)
} else {
if tc.count != len(routes) {
t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes)
} else if tc.count == 1 {
if !tc.expected.Gateway.Equal(routes[0].Gateway) {
t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err)
}
if !routes[0].Destination.Equal(net.IPv6zero) {
t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase)
}
}
}
}
}
func TestParseIP(t *testing.T) {
testCases := []struct {
tcase string
ip string
family AddressFamily
success bool
expected net.IP
}{
{"empty", "", familyIPv4, false, nil},
{"too short", "AA", familyIPv4, false, nil},
{"too long", "0011223344", familyIPv4, false, nil},
{"invalid", "invalid!", familyIPv4, false, nil},
{"zero", "00000000", familyIPv4, true, net.IP{0, 0, 0, 0}},
{"ffff", "FFFFFFFF", familyIPv4, true, net.IP{0xff, 0xff, 0xff, 0xff}},
{"valid v4", "12345678", familyIPv4, true, net.IP{120, 86, 52, 18}},
{"valid v6", "fe800000000000000000000000000000", familyIPv6, true, net.IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{"v6 too short", "fe80000000000000021fcafffea0ec0", familyIPv6, false, nil},
{"v6 too long", "fe80000000000000021fcafffea0ec002", familyIPv6, false, nil},
}
for _, tc := range testCases {
ip, err := parseIP(tc.ip, tc.family)
if !ip.Equal(tc.expected) {
t.Errorf("case[%v]: expected %q, got %q . err : %v", tc.tcase, tc.expected, ip, err)
}
}
}
func TestIsInterfaceUp(t *testing.T) {
testCases := []struct {
tcase string
intf *net.Interface
expected bool
}{
{"up", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}, true},
{"down", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0}, false},
{"no interface", nil, false},
}
for _, tc := range testCases {
it := isInterfaceUp(tc.intf)
if it != tc.expected {
t.Errorf("case[%v]: expected %v, got %v .", tc.tcase, tc.expected, it)
}
}
}
type addrStruct struct{ val string }
func (a addrStruct) Network() string {
return a.val
}
func (a addrStruct) String() string {
return a.val
}
func TestFinalIP(t *testing.T) {
testCases := []struct {
tcase string
addr []net.Addr
family AddressFamily
expected net.IP
}{
{"no ipv4", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv4, nil},
{"no ipv6", []net.Addr{addrStruct{val: "10.128.0.4/32"}}, familyIPv6, nil},
{"invalidV4CIDR", []net.Addr{addrStruct{val: "10.20.30.40.50/24"}}, familyIPv4, nil},
{"invalidV6CIDR", []net.Addr{addrStruct{val: "fe80::2f7:67fff:fe6e:2956/64"}}, familyIPv6, nil},
{"loopback", []net.Addr{addrStruct{val: "127.0.0.1/24"}}, familyIPv4, nil},
{"loopbackv6", []net.Addr{addrStruct{val: "::1/128"}}, familyIPv6, nil},
{"link local v4", []net.Addr{addrStruct{val: "169.254.1.10/16"}}, familyIPv4, nil},
{"link local v6", []net.Addr{addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}}, familyIPv6, nil},
{"ip4", []net.Addr{addrStruct{val: "10.254.12.132/17"}}, familyIPv4, net.ParseIP("10.254.12.132")},
{"ip6", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv6, net.ParseIP("2001::5")},
{"no addresses", []net.Addr{}, familyIPv4, nil},
}
for _, tc := range testCases {
ip, err := getMatchingGlobalIP(tc.addr, tc.family)
if !ip.Equal(tc.expected) {
t.Errorf("case[%v]: expected %v, got %v .err : %v", tc.tcase, tc.expected, ip, err)
}
}
}
func TestAddrs(t *testing.T) {
var nw networkInterfacer = validNetworkInterface{}
intf := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0}
addrs, err := nw.Addrs(&intf)
if err != nil {
t.Errorf("expected no error got : %v", err)
}
if len(addrs) != 2 {
t.Errorf("expected addrs: 2 got null")
}
}
// Has a valid IPv4 address (IPv6 is LLA)
type validNetworkInterface struct {
}
func (_ validNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return &upIntf, nil
}
func (_ validNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{
addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}, addrStruct{val: "10.254.71.145/17"}}
return ifat, nil
}
func (_ validNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf}, nil
}
// Both IPv4 and IPv6 addresses (expecting IPv4 to be used)
type v4v6NetworkInterface struct {
}
func (_ v4v6NetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return &upIntf, nil
}
func (_ v4v6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{
addrStruct{val: "2001::10/64"}, addrStruct{val: "10.254.71.145/17"}}
return ifat, nil
}
func (_ v4v6NetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf}, nil
}
// Interface with only IPv6 address
type ipv6NetworkInterface struct {
}
func (_ ipv6NetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return &upIntf, nil
}
func (_ ipv6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "2001::200/64"}}
return ifat, nil
}
func (_ ipv6NetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf}, nil
}
// Only with link local addresses
type networkInterfaceWithOnlyLinkLocals struct {
}
func (_ networkInterfaceWithOnlyLinkLocals) InterfaceByName(intfName string) (*net.Interface, error) {
return &upIntf, nil
}
func (_ networkInterfaceWithOnlyLinkLocals) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}}
return ifat, nil
}
func (_ networkInterfaceWithOnlyLinkLocals) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf}, nil
}
// Unable to get interface(s)
type failGettingNetworkInterface struct {
}
func (_ failGettingNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return nil, fmt.Errorf("unable get Interface")
}
func (_ failGettingNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
return nil, nil
}
func (_ failGettingNetworkInterface) Interfaces() ([]net.Interface, error) {
return nil, fmt.Errorf("mock failed getting all interfaces")
}
// No interfaces
type noNetworkInterface struct {
}
func (_ noNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return nil, fmt.Errorf("no such network interface")
}
func (_ noNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
return nil, nil
}
func (_ noNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{}, nil
}
// Interface is down
type downNetworkInterface struct {
}
func (_ downNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return &downIntf, nil
}
func (_ downNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{
addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}, addrStruct{val: "10.254.71.145/17"}}
return ifat, nil
}
func (_ downNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{downIntf}, nil
}
// Loopback interface
type loopbackNetworkInterface struct {
}
func (_ loopbackNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return &loopbackIntf, nil
}
func (_ loopbackNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{
addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}}
return ifat, nil
}
func (_ loopbackNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{loopbackIntf}, nil
}
// Point to point interface
type p2pNetworkInterface struct {
}
func (_ p2pNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return &p2pIntf, nil
}
func (_ p2pNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{
addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}}
return ifat, nil
}
func (_ p2pNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{p2pIntf}, nil
}
// Unable to get IP addresses for interface
type networkInterfaceFailGetAddrs struct {
}
func (_ networkInterfaceFailGetAddrs) InterfaceByName(intfName string) (*net.Interface, error) {
return &upIntf, nil
}
func (_ networkInterfaceFailGetAddrs) Addrs(intf *net.Interface) ([]net.Addr, error) {
return nil, fmt.Errorf("unable to get Addrs")
}
func (_ networkInterfaceFailGetAddrs) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf}, nil
}
// No addresses for interface
type networkInterfaceWithNoAddrs struct {
}
func (_ networkInterfaceWithNoAddrs) InterfaceByName(intfName string) (*net.Interface, error) {
return &upIntf, nil
}
func (_ networkInterfaceWithNoAddrs) Addrs(intf *net.Interface) ([]net.Addr, error) {
ifat := []net.Addr{}
return ifat, nil
}
func (_ networkInterfaceWithNoAddrs) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf}, nil
}
// Invalid addresses for interface
type networkInterfaceWithInvalidAddr struct {
}
func (_ networkInterfaceWithInvalidAddr) InterfaceByName(intfName string) (*net.Interface, error) {
return &upIntf, nil
}
func (_ networkInterfaceWithInvalidAddr) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "10.20.30.40.50/24"}}
return ifat, nil
}
func (_ networkInterfaceWithInvalidAddr) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf}, nil
}
func TestGetIPFromInterface(t *testing.T) {
testCases := []struct {
tcase string
nwname string
family AddressFamily
nw networkInterfacer
expected net.IP
errStrFrag string
}{
{"ipv4", "eth3", familyIPv4, validNetworkInterface{}, net.ParseIP("10.254.71.145"), ""},
{"ipv6", "eth3", familyIPv6, ipv6NetworkInterface{}, net.ParseIP("2001::200"), ""},
{"no ipv4", "eth3", familyIPv4, ipv6NetworkInterface{}, nil, ""},
{"no ipv6", "eth3", familyIPv6, validNetworkInterface{}, nil, ""},
{"I/F down", "eth3", familyIPv4, downNetworkInterface{}, nil, ""},
{"I/F get fail", "eth3", familyIPv4, noNetworkInterface{}, nil, "no such network interface"},
{"fail get addr", "eth3", familyIPv4, networkInterfaceFailGetAddrs{}, nil, "unable to get Addrs"},
{"bad addr", "eth3", familyIPv4, networkInterfaceWithInvalidAddr{}, nil, "invalid CIDR"},
}
for _, tc := range testCases {
ip, err := getIPFromInterface(tc.nwname, tc.family, tc.nw)
if err != nil {
if !strings.Contains(err.Error(), tc.errStrFrag) {
t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag)
}
} else if tc.errStrFrag != "" {
t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag)
} else if !ip.Equal(tc.expected) {
t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err)
}
}
}
func TestChooseHostInterfaceFromRoute(t *testing.T) {
testCases := []struct {
tcase string
routes []Route
nw networkInterfacer
expected net.IP
}{
{"ipv4", routeV4, validNetworkInterface{}, net.ParseIP("10.254.71.145")},
{"ipv6", routeV6, ipv6NetworkInterface{}, net.ParseIP("2001::200")},
{"prefer ipv4", bothRoutes, v4v6NetworkInterface{}, net.ParseIP("10.254.71.145")},
{"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, nil},
{"no routes", noRoutes, validNetworkInterface{}, nil},
{"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, nil},
}
for _, tc := range testCases {
ip, err := chooseHostInterfaceFromRoute(tc.routes, tc.nw)
if !ip.Equal(tc.expected) {
t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err)
}
}
}
func TestMemberOf(t *testing.T) {
testCases := []struct {
tcase string
ip net.IP
family AddressFamily
expected bool
}{
{"ipv4 is 4", net.ParseIP("10.20.30.40"), familyIPv4, true},
{"ipv4 is 6", net.ParseIP("10.10.10.10"), familyIPv6, false},
{"ipv6 is 4", net.ParseIP("2001::100"), familyIPv4, false},
{"ipv6 is 6", net.ParseIP("2001::100"), familyIPv6, true},
}
for _, tc := range testCases {
if memberOf(tc.ip, tc.family) != tc.expected {
t.Errorf("case[%s]: expected %+v", tc.tcase, tc.expected)
}
}
}
func TestGetIPFromHostInterfaces(t *testing.T) {
testCases := []struct {
tcase string
nw networkInterfacer
expected net.IP
errStrFrag string
}{
{"fail get I/Fs", failGettingNetworkInterface{}, nil, "failed getting all interfaces"},
{"no interfaces", noNetworkInterface{}, nil, "no interfaces"},
{"I/F not up", downNetworkInterface{}, nil, "no acceptable"},
{"loopback only", loopbackNetworkInterface{}, nil, "no acceptable"},
{"P2P I/F only", p2pNetworkInterface{}, nil, "no acceptable"},
{"fail get addrs", networkInterfaceFailGetAddrs{}, nil, "unable to get Addrs"},
{"no addresses", networkInterfaceWithNoAddrs{}, nil, "no acceptable"},
{"invalid addr", networkInterfaceWithInvalidAddr{}, nil, "invalid CIDR"},
{"no matches", networkInterfaceWithOnlyLinkLocals{}, nil, "no acceptable"},
{"ipv4", validNetworkInterface{}, net.ParseIP("10.254.71.145"), ""},
{"ipv6", ipv6NetworkInterface{}, net.ParseIP("2001::200"), ""},
}
for _, tc := range testCases {
ip, err := chooseIPFromHostInterfaces(tc.nw)
if !ip.Equal(tc.expected) {
t.Errorf("case[%s]: expected %+v, got %+v with err : %v", tc.tcase, tc.expected, ip, err)
}
if err != nil && !strings.Contains(err.Error(), tc.errStrFrag) {
t.Errorf("case[%s]: unable to find %q in error string %q", tc.tcase, tc.errStrFrag, err.Error())
}
}
}
func makeRouteFile(content string, t *testing.T) (*os.File, error) {
routeFile, err := ioutil.TempFile("", "route")
if err != nil {
return nil, err
}
if _, err := routeFile.Write([]byte(content)); err != nil {
return routeFile, err
}
err = routeFile.Close()
return routeFile, err
}
func TestFailGettingIPv4Routes(t *testing.T) {
defer func() { v4File.name = ipv4RouteFile }()
// Try failure to open file (should not occur, as caller ensures we have IPv4 route file, but being thorough)
v4File.name = "no-such-file"
errStrFrag := "no such file"
_, err := v4File.extract()
if err == nil {
fmt.Errorf("Expected error trying to read non-existent v4 route file")
}
if !strings.Contains(err.Error(), errStrFrag) {
t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error())
}
}
func TestFailGettingIPv6Routes(t *testing.T) {
defer func() { v6File.name = ipv6RouteFile }()
// Try failure to open file (this would be ignored by caller)
v6File.name = "no-such-file"
errStrFrag := "no such file"
_, err := v6File.extract()
if err == nil {
fmt.Errorf("Expected error trying to read non-existent v6 route file")
}
if !strings.Contains(err.Error(), errStrFrag) {
t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error())
}
}
func TestGetAllDefaultRoutesFailNoV4RouteFile(t *testing.T) {
defer func() { v4File.name = ipv4RouteFile }()
// Should not occur, as caller ensures we have IPv4 route file, but being thorough
v4File.name = "no-such-file"
errStrFrag := "no such file"
_, err := getAllDefaultRoutes()
if err == nil {
fmt.Errorf("Expected error trying to read non-existent v4 route file")
}
if !strings.Contains(err.Error(), errStrFrag) {
t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error())
}
}
func TestGetAllDefaultRoutes(t *testing.T) {
testCases := []struct {
tcase string
v4Info string
v6Info string
count int
expected []Route
errStrFrag string
}{
{"no routes", noInternetConnection, v6noDefaultRoutes, 0, nil, "No default routes"},
{"only v4 route", gatewayfirst, v6noDefaultRoutes, 1, routeV4, ""},
{"only v6 route", noInternetConnection, v6gatewayfirst, 1, routeV6, ""},
{"v4 and v6 routes", gatewayfirst, v6gatewayfirst, 2, bothRoutes, ""},
}
defer func() {
v4File.name = ipv4RouteFile
v6File.name = ipv6RouteFile
}()
for _, tc := range testCases {
routeFile, err := makeRouteFile(tc.v4Info, t)
if routeFile != nil {
defer os.Remove(routeFile.Name())
}
if err != nil {
t.Errorf("case[%s]: test setup failure for IPv4 route file: %v", tc.tcase, err)
}
v4File.name = routeFile.Name()
v6routeFile, err := makeRouteFile(tc.v6Info, t)
if v6routeFile != nil {
defer os.Remove(v6routeFile.Name())
}
if err != nil {
t.Errorf("case[%s]: test setup failure for IPv6 route file: %v", tc.tcase, err)
}
v6File.name = v6routeFile.Name()
routes, err := getAllDefaultRoutes()
if err != nil {
if !strings.Contains(err.Error(), tc.errStrFrag) {
t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag)
}
} else if tc.errStrFrag != "" {
t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag)
} else {
if tc.count != len(routes) {
t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes)
}
for i, expected := range tc.expected {
if !expected.Gateway.Equal(routes[i].Gateway) {
t.Errorf("case[%s]: at %d expected %v, got %v .err : %v", tc.tcase, i, tc.expected, routes, err)
}
zeroIP := net.IPv4zero
if expected.Family == familyIPv6 {
zeroIP = net.IPv6zero
}
if !routes[i].Destination.Equal(zeroIP) {
t.Errorf("case[%s}: at %d destination is not for default route (not %v)", tc.tcase, i, zeroIP)
}
}
}
}
}

113
vendor/k8s.io/apimachinery/pkg/util/net/port_range.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
/*
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 net
import (
"fmt"
"strconv"
"strings"
)
// PortRange represents a range of TCP/UDP ports. To represent a single port,
// set Size to 1.
type PortRange struct {
Base int
Size int
}
// Contains tests whether a given port falls within the PortRange.
func (pr *PortRange) Contains(p int) bool {
return (p >= pr.Base) && ((p - pr.Base) < pr.Size)
}
// String converts the PortRange to a string representation, which can be
// parsed by PortRange.Set or ParsePortRange.
func (pr PortRange) String() string {
if pr.Size == 0 {
return ""
}
return fmt.Sprintf("%d-%d", pr.Base, pr.Base+pr.Size-1)
}
// Set parses a string of the form "min-max", inclusive at both ends, and
// sets the PortRange from it. This is part of the flag.Value and pflag.Value
// interfaces.
func (pr *PortRange) Set(value string) error {
value = strings.TrimSpace(value)
// TODO: Accept "80" syntax
// TODO: Accept "80+8" syntax
if value == "" {
pr.Base = 0
pr.Size = 0
return nil
}
hyphenIndex := strings.Index(value, "-")
if hyphenIndex == -1 {
return fmt.Errorf("expected hyphen in port range")
}
var err error
var low int
var high int
low, err = strconv.Atoi(value[:hyphenIndex])
if err == nil {
high, err = strconv.Atoi(value[hyphenIndex+1:])
}
if err != nil {
return fmt.Errorf("unable to parse port range: %s: %v", value, err)
}
if low > 65535 || high > 65535 {
return fmt.Errorf("the port range cannot be greater than 65535: %s", value)
}
if high < low {
return fmt.Errorf("end port cannot be less than start port: %s", value)
}
pr.Base = low
pr.Size = 1 + high - low
return nil
}
// Type returns a descriptive string about this type. This is part of the
// pflag.Value interface.
func (*PortRange) Type() string {
return "portRange"
}
// ParsePortRange parses a string of the form "min-max", inclusive at both
// ends, and initializs a new PortRange from it.
func ParsePortRange(value string) (*PortRange, error) {
pr := &PortRange{}
err := pr.Set(value)
if err != nil {
return nil, err
}
return pr, nil
}
func ParsePortRangeOrDie(value string) *PortRange {
pr, err := ParsePortRange(value)
if err != nil {
panic(fmt.Sprintf("couldn't parse port range %q: %v", value, err))
}
return pr
}

View File

@ -0,0 +1,69 @@
/*
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 net
import (
"testing"
flag "github.com/spf13/pflag"
)
func TestPortRange(t *testing.T) {
testCases := []struct {
input string
success bool
expected string
included int
excluded int
}{
{"100-200", true, "100-200", 200, 201},
{" 100-200 ", true, "100-200", 200, 201},
{"0-0", true, "0-0", 0, 1},
{"", true, "", -1, 0},
{"100", false, "", -1, -1},
{"100 - 200", false, "", -1, -1},
{"-100", false, "", -1, -1},
{"100-", false, "", -1, -1},
{"200-100", false, "", -1, -1},
{"60000-70000", false, "", -1, -1},
{"70000-80000", false, "", -1, -1},
}
for i := range testCases {
tc := &testCases[i]
pr := &PortRange{}
var f flag.Value = pr
err := f.Set(tc.input)
if err != nil && tc.success == true {
t.Errorf("expected success, got %q", err)
continue
} else if err == nil && tc.success == false {
t.Errorf("expected failure")
continue
} else if tc.success {
if f.String() != tc.expected {
t.Errorf("expected %q, got %q", tc.expected, f.String())
}
if tc.included >= 0 && !pr.Contains(tc.included) {
t.Errorf("expected %q to include %d", f.String(), tc.included)
}
if tc.excluded >= 0 && pr.Contains(tc.excluded) {
t.Errorf("expected %q to exclude %d", f.String(), tc.excluded)
}
}
}
}

77
vendor/k8s.io/apimachinery/pkg/util/net/port_split.go generated vendored Normal file
View File

@ -0,0 +1,77 @@
/*
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 net
import (
"strings"
"k8s.io/apimachinery/pkg/util/sets"
)
var validSchemes = sets.NewString("http", "https", "")
// SplitSchemeNamePort takes a string of the following forms:
// * "<name>", returns "", "<name>","", true
// * "<name>:<port>", returns "", "<name>","<port>",true
// * "<scheme>:<name>:<port>", returns "<scheme>","<name>","<port>",true
//
// Name must be non-empty or valid will be returned false.
// Scheme must be "http" or "https" if specified
// Port is returned as a string, and it is not required to be numeric (could be
// used for a named port, for example).
func SplitSchemeNamePort(id string) (scheme, name, port string, valid bool) {
parts := strings.Split(id, ":")
switch len(parts) {
case 1:
name = parts[0]
case 2:
name = parts[0]
port = parts[1]
case 3:
scheme = parts[0]
name = parts[1]
port = parts[2]
default:
return "", "", "", false
}
if len(name) > 0 && validSchemes.Has(scheme) {
return scheme, name, port, true
} else {
return "", "", "", false
}
}
// JoinSchemeNamePort returns a string that specifies the scheme, name, and port:
// * "<name>"
// * "<name>:<port>"
// * "<scheme>:<name>:<port>"
// None of the parameters may contain a ':' character
// Name is required
// Scheme must be "", "http", or "https"
func JoinSchemeNamePort(scheme, name, port string) string {
if len(scheme) > 0 {
// Must include three segments to specify scheme
return scheme + ":" + name + ":" + port
}
if len(port) > 0 {
// Must include two segments to specify port
return name + ":" + port
}
// Return name alone
return name
}

View File

@ -0,0 +1,121 @@
/*
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 net
import (
"testing"
)
func TestSplitSchemeNamePort(t *testing.T) {
table := []struct {
in string
name, port, scheme string
valid bool
normalized bool
}{
{
in: "aoeu:asdf",
name: "aoeu",
port: "asdf",
valid: true,
normalized: true,
}, {
in: "http:aoeu:asdf",
scheme: "http",
name: "aoeu",
port: "asdf",
valid: true,
normalized: true,
}, {
in: "https:aoeu:",
scheme: "https",
name: "aoeu",
port: "",
valid: true,
normalized: false,
}, {
in: "https:aoeu:asdf",
scheme: "https",
name: "aoeu",
port: "asdf",
valid: true,
normalized: true,
}, {
in: "aoeu:",
name: "aoeu",
valid: true,
normalized: false,
}, {
in: "aoeu",
name: "aoeu",
valid: true,
normalized: true,
}, {
in: ":asdf",
valid: false,
}, {
in: "aoeu:asdf:htns",
valid: false,
}, {
in: "http::asdf",
valid: false,
}, {
in: "http::",
valid: false,
}, {
in: "",
valid: false,
},
}
for _, item := range table {
scheme, name, port, valid := SplitSchemeNamePort(item.in)
if e, a := item.scheme, scheme; e != a {
t.Errorf("%q: Wanted %q, got %q", item.in, e, a)
}
if e, a := item.name, name; e != a {
t.Errorf("%q: Wanted %q, got %q", item.in, e, a)
}
if e, a := item.port, port; e != a {
t.Errorf("%q: Wanted %q, got %q", item.in, e, a)
}
if e, a := item.valid, valid; e != a {
t.Errorf("%q: Wanted %t, got %t", item.in, e, a)
}
// Make sure valid items round trip through JoinSchemeNamePort
if item.valid {
out := JoinSchemeNamePort(scheme, name, port)
if item.normalized && out != item.in {
t.Errorf("%q: Wanted %s, got %s", item.in, item.in, out)
}
scheme, name, port, valid := SplitSchemeNamePort(out)
if e, a := item.scheme, scheme; e != a {
t.Errorf("%q: Wanted %q, got %q", item.in, e, a)
}
if e, a := item.name, name; e != a {
t.Errorf("%q: Wanted %q, got %q", item.in, e, a)
}
if e, a := item.port, port; e != a {
t.Errorf("%q: Wanted %q, got %q", item.in, e, a)
}
if e, a := item.valid, valid; e != a {
t.Errorf("%q: Wanted %t, got %t", item.in, e, a)
}
}
}
}

46
vendor/k8s.io/apimachinery/pkg/util/net/util.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
/*
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 net
import (
"net"
"reflect"
"syscall"
)
// IPNetEqual checks if the two input IPNets are representing the same subnet.
// For example,
// 10.0.0.1/24 and 10.0.0.0/24 are the same subnet.
// 10.0.0.1/24 and 10.0.0.0/25 are not the same subnet.
func IPNetEqual(ipnet1, ipnet2 *net.IPNet) bool {
if ipnet1 == nil || ipnet2 == nil {
return false
}
if reflect.DeepEqual(ipnet1.Mask, ipnet2.Mask) && ipnet1.Contains(ipnet2.IP) && ipnet2.Contains(ipnet1.IP) {
return true
}
return false
}
// Returns if the given err is "connection reset by peer" error.
func IsConnectionReset(err error) bool {
opErr, ok := err.(*net.OpError)
if ok && opErr.Err.Error() == syscall.ECONNRESET.Error() {
return true
}
return false
}

68
vendor/k8s.io/apimachinery/pkg/util/net/util_test.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
/*
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 net
import (
"net"
"testing"
)
func getIPNet(cidr string) *net.IPNet {
_, ipnet, _ := net.ParseCIDR(cidr)
return ipnet
}
func TestIPNetEqual(t *testing.T) {
testCases := []struct {
ipnet1 *net.IPNet
ipnet2 *net.IPNet
expect bool
}{
//null case
{
getIPNet("10.0.0.1/24"),
getIPNet(""),
false,
},
{
getIPNet("10.0.0.0/24"),
getIPNet("10.0.0.0/24"),
true,
},
{
getIPNet("10.0.0.0/24"),
getIPNet("10.0.0.1/24"),
true,
},
{
getIPNet("10.0.0.0/25"),
getIPNet("10.0.0.0/24"),
false,
},
{
getIPNet("10.0.1.0/24"),
getIPNet("10.0.0.0/24"),
false,
},
}
for _, tc := range testCases {
if tc.expect != IPNetEqual(tc.ipnet1, tc.ipnet2) {
t.Errorf("Expect equality of %s and %s be to %v", tc.ipnet1.String(), tc.ipnet2.String(), tc.expect)
}
}
}

62
vendor/k8s.io/apimachinery/pkg/util/proxy/BUILD generated vendored Normal file
View File

@ -0,0 +1,62 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"dial_test.go",
"transport_test.go",
"upgradeaware_test.go",
],
importpath = "k8s.io/apimachinery/pkg/util/proxy",
library = ":go_default_library",
deps = [
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/golang.org/x/net/websocket:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dial.go",
"doc.go",
"transport.go",
"upgradeaware.go",
],
importpath = "k8s.io/apimachinery/pkg/util/proxy",
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/mxk/go-flowrate/flowrate:go_default_library",
"//vendor/golang.org/x/net/html:go_default_library",
"//vendor/golang.org/x/net/html/atom:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/third_party/forked/golang/netutil:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

115
vendor/k8s.io/apimachinery/pkg/util/proxy/dial.go generated vendored Normal file
View File

@ -0,0 +1,115 @@
/*
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 proxy
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"github.com/golang/glog"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/third_party/forked/golang/netutil"
)
func DialURL(url *url.URL, transport http.RoundTripper) (net.Conn, error) {
dialAddr := netutil.CanonicalAddr(url)
dialer, err := utilnet.DialerFor(transport)
if err != nil {
glog.V(5).Infof("Unable to unwrap transport %T to get dialer: %v", transport, err)
}
switch url.Scheme {
case "http":
if dialer != nil {
return dialer("tcp", dialAddr)
}
return net.Dial("tcp", dialAddr)
case "https":
// Get the tls config from the transport if we recognize it
var tlsConfig *tls.Config
var tlsConn *tls.Conn
var err error
tlsConfig, err = utilnet.TLSClientConfig(transport)
if err != nil {
glog.V(5).Infof("Unable to unwrap transport %T to get at TLS config: %v", transport, err)
}
if dialer != nil {
// We have a dialer; use it to open the connection, then
// create a tls client using the connection.
netConn, err := dialer("tcp", dialAddr)
if err != nil {
return nil, err
}
if tlsConfig == nil {
// tls.Client requires non-nil config
glog.Warningf("using custom dialer with no TLSClientConfig. Defaulting to InsecureSkipVerify")
// tls.Handshake() requires ServerName or InsecureSkipVerify
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
} else if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
// tls.Handshake() requires ServerName or InsecureSkipVerify
// infer the ServerName from the hostname we're connecting to.
inferredHost := dialAddr
if host, _, err := net.SplitHostPort(dialAddr); err == nil {
inferredHost = host
}
// Make a copy to avoid polluting the provided config
tlsConfigCopy := tlsConfig.Clone()
tlsConfigCopy.ServerName = inferredHost
tlsConfig = tlsConfigCopy
}
tlsConn = tls.Client(netConn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
netConn.Close()
return nil, err
}
} else {
// Dial
tlsConn, err = tls.Dial("tcp", dialAddr, tlsConfig)
if err != nil {
return nil, err
}
}
// Return if we were configured to skip validation
if tlsConfig != nil && tlsConfig.InsecureSkipVerify {
return tlsConn, nil
}
// Verify
host, _, _ := net.SplitHostPort(dialAddr)
if tlsConfig != nil && len(tlsConfig.ServerName) > 0 {
host = tlsConfig.ServerName
}
if err := tlsConn.VerifyHostname(host); err != nil {
tlsConn.Close()
return nil, err
}
return tlsConn, nil
default:
return nil, fmt.Errorf("Unknown scheme: %s", url.Scheme)
}
}

176
vendor/k8s.io/apimachinery/pkg/util/proxy/dial_test.go generated vendored Normal file
View File

@ -0,0 +1,176 @@
/*
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 proxy
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
utilnet "k8s.io/apimachinery/pkg/util/net"
)
func TestDialURL(t *testing.T) {
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM(localhostCert) {
t.Fatal("error setting up localhostCert pool")
}
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
t.Fatal(err)
}
testcases := map[string]struct {
TLSConfig *tls.Config
Dial utilnet.DialFunc
ExpectError string
}{
"insecure": {
TLSConfig: &tls.Config{InsecureSkipVerify: true},
},
"secure, no roots": {
TLSConfig: &tls.Config{InsecureSkipVerify: false},
ExpectError: "unknown authority",
},
"secure with roots": {
TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots},
},
"secure with mismatched server": {
TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"},
ExpectError: "not bogus.com",
},
"secure with matched server": {
TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"},
},
"insecure, custom dial": {
TLSConfig: &tls.Config{InsecureSkipVerify: true},
Dial: net.Dial,
},
"secure, no roots, custom dial": {
TLSConfig: &tls.Config{InsecureSkipVerify: false},
Dial: net.Dial,
ExpectError: "unknown authority",
},
"secure with roots, custom dial": {
TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots},
Dial: net.Dial,
},
"secure with mismatched server, custom dial": {
TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"},
Dial: net.Dial,
ExpectError: "not bogus.com",
},
"secure with matched server, custom dial": {
TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"},
Dial: net.Dial,
},
}
for k, tc := range testcases {
func() {
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}))
defer ts.Close()
ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
ts.StartTLS()
// Make a copy of the config
tlsConfigCopy := tc.TLSConfig.Clone()
// Clone() mutates the receiver (!), so also call it on the copy
tlsConfigCopy.Clone()
transport := &http.Transport{
Dial: tc.Dial,
TLSClientConfig: tlsConfigCopy,
}
extractedDial, err := utilnet.DialerFor(transport)
if err != nil {
t.Fatal(err)
}
if fmt.Sprintf("%p", extractedDial) != fmt.Sprintf("%p", tc.Dial) {
t.Fatalf("%s: Unexpected dial", k)
}
extractedTLSConfig, err := utilnet.TLSClientConfig(transport)
if err != nil {
t.Fatal(err)
}
if extractedTLSConfig == nil {
t.Fatalf("%s: Expected tlsConfig", k)
}
u, _ := url.Parse(ts.URL)
_, p, _ := net.SplitHostPort(u.Host)
u.Host = net.JoinHostPort("127.0.0.1", p)
conn, err := DialURL(u, transport)
// Make sure dialing doesn't mutate the transport's TLSConfig
if !reflect.DeepEqual(tc.TLSConfig, tlsConfigCopy) {
t.Errorf("%s: transport's copy of TLSConfig was mutated\n%s", k, diff.ObjectReflectDiff(tc.TLSConfig, tlsConfigCopy))
}
if err != nil {
if tc.ExpectError == "" {
t.Errorf("%s: expected no error, got %q", k, err.Error())
}
if !strings.Contains(err.Error(), tc.ExpectError) {
t.Errorf("%s: expected error containing %q, got %q", k, tc.ExpectError, err.Error())
}
return
}
conn.Close()
if tc.ExpectError != "" {
t.Errorf("%s: expected error %q, got none", k, tc.ExpectError)
}
}()
}
}
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIIBjzCCATmgAwIBAgIRAKpi2WmTcFrVjxrl5n5YDUEwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
QQC9fEbRszP3t14Gr4oahV7zFObBI4TfA5i7YnlMXeLinb7MnvT4bkfOJzE6zktn
59zP7UiHs3l4YOuqrjiwM413AgMBAAGjaDBmMA4GA1UdDwEB/wQEAwICpDATBgNV
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MC4GA1UdEQQnMCWCC2V4
YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUA
A0EAUsVE6KMnza/ZbodLlyeMzdo7EM/5nb5ywyOxgIOCf0OOLHsPS9ueGLQX9HEG
//yjTXuhNcUugExIjM/AIwAZPQ==
-----END CERTIFICATE-----`)
// localhostKey is the private key for localhostCert.
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAL18RtGzM/e3XgavihqFXvMU5sEjhN8DmLtieUxd4uKdvsye9Phu
R84nMTrOS2fn3M/tSIezeXhg66quOLAzjXcCAwEAAQJBAKcRxH9wuglYLBdI/0OT
BLzfWPZCEw1vZmMR2FF1Fm8nkNOVDPleeVGTWoOEcYYlQbpTmkGSxJ6ya+hqRi6x
goECIQDx3+X49fwpL6B5qpJIJMyZBSCuMhH4B7JevhGGFENi3wIhAMiNJN5Q3UkL
IuSvv03kaPR5XVQ99/UeEetUgGvBcABpAiBJSBzVITIVCGkGc7d+RCf49KTCIklv
bGWObufAR8Ni4QIgWpILjW8dkGg8GOUZ0zaNA6Nvt6TIv2UWGJ4v5PoV98kCIQDx
rIiZs5QbKdycsv9gQJzwQAogC8o04X3Zz3dsoX+h4A==
-----END RSA PRIVATE KEY-----`)

18
vendor/k8s.io/apimachinery/pkg/util/proxy/doc.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
/*
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 proxy provides transport and upgrade support for proxies.
package proxy // import "k8s.io/apimachinery/pkg/util/proxy"

259
vendor/k8s.io/apimachinery/pkg/util/proxy/transport.go generated vendored Normal file
View File

@ -0,0 +1,259 @@
/*
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 proxy
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"github.com/golang/glog"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
"k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets"
)
// atomsToAttrs states which attributes of which tags require URL substitution.
// Sources: http://www.w3.org/TR/REC-html40/index/attributes.html
// http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1
var atomsToAttrs = map[atom.Atom]sets.String{
atom.A: sets.NewString("href"),
atom.Applet: sets.NewString("codebase"),
atom.Area: sets.NewString("href"),
atom.Audio: sets.NewString("src"),
atom.Base: sets.NewString("href"),
atom.Blockquote: sets.NewString("cite"),
atom.Body: sets.NewString("background"),
atom.Button: sets.NewString("formaction"),
atom.Command: sets.NewString("icon"),
atom.Del: sets.NewString("cite"),
atom.Embed: sets.NewString("src"),
atom.Form: sets.NewString("action"),
atom.Frame: sets.NewString("longdesc", "src"),
atom.Head: sets.NewString("profile"),
atom.Html: sets.NewString("manifest"),
atom.Iframe: sets.NewString("longdesc", "src"),
atom.Img: sets.NewString("longdesc", "src", "usemap"),
atom.Input: sets.NewString("src", "usemap", "formaction"),
atom.Ins: sets.NewString("cite"),
atom.Link: sets.NewString("href"),
atom.Object: sets.NewString("classid", "codebase", "data", "usemap"),
atom.Q: sets.NewString("cite"),
atom.Script: sets.NewString("src"),
atom.Source: sets.NewString("src"),
atom.Video: sets.NewString("poster", "src"),
// TODO: css URLs hidden in style elements.
}
// Transport is a transport for text/html content that replaces URLs in html
// content with the prefix of the proxy server
type Transport struct {
Scheme string
Host string
PathPrepend string
http.RoundTripper
}
// RoundTrip implements the http.RoundTripper interface
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
// Add reverse proxy headers.
forwardedURI := path.Join(t.PathPrepend, req.URL.Path)
if strings.HasSuffix(req.URL.Path, "/") {
forwardedURI = forwardedURI + "/"
}
req.Header.Set("X-Forwarded-Uri", forwardedURI)
if len(t.Host) > 0 {
req.Header.Set("X-Forwarded-Host", t.Host)
}
if len(t.Scheme) > 0 {
req.Header.Set("X-Forwarded-Proto", t.Scheme)
}
rt := t.RoundTripper
if rt == nil {
rt = http.DefaultTransport
}
resp, err := rt.RoundTrip(req)
if err != nil {
message := fmt.Sprintf("Error: '%s'\nTrying to reach: '%v'", err.Error(), req.URL.String())
resp = &http.Response{
StatusCode: http.StatusServiceUnavailable,
Body: ioutil.NopCloser(strings.NewReader(message)),
}
return resp, nil
}
if redirect := resp.Header.Get("Location"); redirect != "" {
resp.Header.Set("Location", t.rewriteURL(redirect, req.URL, req.Host))
return resp, nil
}
cType := resp.Header.Get("Content-Type")
cType = strings.TrimSpace(strings.SplitN(cType, ";", 2)[0])
if cType != "text/html" {
// Do nothing, simply pass through
return resp, nil
}
return t.rewriteResponse(req, resp)
}
var _ = net.RoundTripperWrapper(&Transport{})
func (rt *Transport) WrappedRoundTripper() http.RoundTripper {
return rt.RoundTripper
}
// rewriteURL rewrites a single URL to go through the proxy, if the URL refers
// to the same host as sourceURL, which is the page on which the target URL
// occurred, or if the URL matches the sourceRequestHost. If any error occurs (e.g.
// parsing), it returns targetURL.
func (t *Transport) rewriteURL(targetURL string, sourceURL *url.URL, sourceRequestHost string) string {
url, err := url.Parse(targetURL)
if err != nil {
return targetURL
}
// Example:
// When API server processes a proxy request to a service (e.g. /api/v1/namespace/foo/service/bar/proxy/),
// the sourceURL.Host (i.e. req.URL.Host) is the endpoint IP address of the service. The
// sourceRequestHost (i.e. req.Host) is the Host header that specifies the host on which the
// URL is sought, which can be different from sourceURL.Host. For example, if user sends the
// request through "kubectl proxy" locally (i.e. localhost:8001/api/v1/namespace/foo/service/bar/proxy/),
// sourceRequestHost is "localhost:8001".
//
// If the service's response URL contains non-empty host, and url.Host is equal to either sourceURL.Host
// or sourceRequestHost, we should not consider the returned URL to be a completely different host.
// It's the API server's responsibility to rewrite a same-host-and-absolute-path URL and append the
// necessary URL prefix (i.e. /api/v1/namespace/foo/service/bar/proxy/).
isDifferentHost := url.Host != "" && url.Host != sourceURL.Host && url.Host != sourceRequestHost
isRelative := !strings.HasPrefix(url.Path, "/")
if isDifferentHost || isRelative {
return targetURL
}
// Do not rewrite scheme and host if the Transport has empty scheme and host
// when targetURL already contains the sourceRequestHost
if !(url.Host == sourceRequestHost && t.Scheme == "" && t.Host == "") {
url.Scheme = t.Scheme
url.Host = t.Host
}
origPath := url.Path
// Do not rewrite URL if the sourceURL already contains the necessary prefix.
if strings.HasPrefix(url.Path, t.PathPrepend) {
return url.String()
}
url.Path = path.Join(t.PathPrepend, url.Path)
if strings.HasSuffix(origPath, "/") {
// Add back the trailing slash, which was stripped by path.Join().
url.Path += "/"
}
return url.String()
}
// rewriteHTML scans the HTML for tags with url-valued attributes, and updates
// those values with the urlRewriter function. The updated HTML is output to the
// writer.
func rewriteHTML(reader io.Reader, writer io.Writer, urlRewriter func(string) string) error {
// Note: This assumes the content is UTF-8.
tokenizer := html.NewTokenizer(reader)
var err error
for err == nil {
tokenType := tokenizer.Next()
switch tokenType {
case html.ErrorToken:
err = tokenizer.Err()
case html.StartTagToken, html.SelfClosingTagToken:
token := tokenizer.Token()
if urlAttrs, ok := atomsToAttrs[token.DataAtom]; ok {
for i, attr := range token.Attr {
if urlAttrs.Has(attr.Key) {
token.Attr[i].Val = urlRewriter(attr.Val)
}
}
}
_, err = writer.Write([]byte(token.String()))
default:
_, err = writer.Write(tokenizer.Raw())
}
}
if err != io.EOF {
return err
}
return nil
}
// rewriteResponse modifies an HTML response by updating absolute links referring
// to the original host to instead refer to the proxy transport.
func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*http.Response, error) {
origBody := resp.Body
defer origBody.Close()
newContent := &bytes.Buffer{}
var reader io.Reader = origBody
var writer io.Writer = newContent
encoding := resp.Header.Get("Content-Encoding")
switch encoding {
case "gzip":
var err error
reader, err = gzip.NewReader(reader)
if err != nil {
return nil, fmt.Errorf("errorf making gzip reader: %v", err)
}
gzw := gzip.NewWriter(writer)
defer gzw.Close()
writer = gzw
// TODO: support flate, other encodings.
case "":
// This is fine
default:
// Some encoding we don't understand-- don't try to parse this
glog.Errorf("Proxy encountered encoding %v for text/html; can't understand this so not fixing links.", encoding)
return resp, nil
}
urlRewriter := func(targetUrl string) string {
return t.rewriteURL(targetUrl, req.URL, req.Host)
}
err := rewriteHTML(reader, writer, urlRewriter)
if err != nil {
glog.Errorf("Failed to rewrite URLs: %v", err)
return resp, err
}
resp.Body = ioutil.NopCloser(newContent)
// Update header node with new content-length
// TODO: Remove any hash/signature headers here?
resp.Header.Del("Content-Length")
resp.ContentLength = int64(newContent.Len())
return resp, err
}

View File

@ -0,0 +1,276 @@
/*
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 proxy
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
func parseURLOrDie(inURL string) *url.URL {
parsed, err := url.Parse(inURL)
if err != nil {
panic(err)
}
return parsed
}
func TestProxyTransport(t *testing.T) {
testTransport := &Transport{
Scheme: "http",
Host: "foo.com",
PathPrepend: "/proxy/node/node1:10250",
}
testTransport2 := &Transport{
Scheme: "https",
Host: "foo.com",
PathPrepend: "/proxy/node/node1:8080",
}
emptyHostTransport := &Transport{
Scheme: "https",
PathPrepend: "/proxy/node/node1:10250",
}
emptySchemeTransport := &Transport{
Host: "foo.com",
PathPrepend: "/proxy/node/node1:10250",
}
emptyHostAndSchemeTransport := &Transport{
PathPrepend: "/proxy/node/node1:10250",
}
type Item struct {
input string
sourceURL string
transport *Transport
output string
contentType string
forwardedURI string
redirect string
redirectWant string
reqHost string
}
table := map[string]Item{
"normal": {
input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`,
sourceURL: "http://mynode.com/logs/log.log",
transport: testTransport,
output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
"full document": {
input: `<html><header></header><body><pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre></body></html>`,
sourceURL: "http://mynode.com/logs/log.log",
transport: testTransport,
output: `<html><header></header><body><pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre></body></html>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
"trailing slash": {
input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log/">google.log</a></pre>`,
sourceURL: "http://mynode.com/logs/log.log",
transport: testTransport,
output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log/">google.log</a></pre>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
"content-type charset": {
input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`,
sourceURL: "http://mynode.com/logs/log.log",
transport: testTransport,
output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`,
contentType: "text/html; charset=utf-8",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
"content-type passthrough": {
input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`,
sourceURL: "http://mynode.com/logs/log.log",
transport: testTransport,
output: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`,
contentType: "text/plain",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
"subdir": {
input: `<a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a>`,
sourceURL: "http://mynode.com/whatever/apt/somelog.log",
transport: testTransport2,
output: `<a href="kubelet.log">kubelet.log</a><a href="https://foo.com/proxy/node/node1:8080/google.log">google.log</a>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:8080/whatever/apt/somelog.log",
},
"image": {
input: `<pre><img src="kubernetes.jpg"/><img src="/kubernetes_abs.jpg"/></pre>`,
sourceURL: "http://mynode.com/",
transport: testTransport,
output: `<pre><img src="kubernetes.jpg"/><img src="http://foo.com/proxy/node/node1:10250/kubernetes_abs.jpg"/></pre>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/",
},
"abs": {
input: `<script src="http://google.com/kubernetes.js"/>`,
sourceURL: "http://mynode.com/any/path/",
transport: testTransport,
output: `<script src="http://google.com/kubernetes.js"/>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/any/path/",
},
"abs but same host": {
input: `<script src="http://mynode.com/kubernetes.js"/>`,
sourceURL: "http://mynode.com/any/path/",
transport: testTransport,
output: `<script src="http://foo.com/proxy/node/node1:10250/kubernetes.js"/>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/any/path/",
},
"redirect rel": {
sourceURL: "http://mynode.com/redirect",
transport: testTransport,
redirect: "/redirected/target/",
redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/",
forwardedURI: "/proxy/node/node1:10250/redirect",
},
"redirect abs same host": {
sourceURL: "http://mynode.com/redirect",
transport: testTransport,
redirect: "http://mynode.com/redirected/target/",
redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/",
forwardedURI: "/proxy/node/node1:10250/redirect",
},
"redirect abs other host": {
sourceURL: "http://mynode.com/redirect",
transport: testTransport,
redirect: "http://example.com/redirected/target/",
redirectWant: "http://example.com/redirected/target/",
forwardedURI: "/proxy/node/node1:10250/redirect",
},
"redirect abs use reqHost no host no scheme": {
sourceURL: "http://mynode.com/redirect",
transport: emptyHostAndSchemeTransport,
redirect: "http://10.0.0.1:8001/redirected/target/",
redirectWant: "http://10.0.0.1:8001/proxy/node/node1:10250/redirected/target/",
forwardedURI: "/proxy/node/node1:10250/redirect",
reqHost: "10.0.0.1:8001",
},
"source contains the redirect already": {
input: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`,
sourceURL: "http://foo.com/logs/log.log",
transport: testTransport,
output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`,
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
"no host": {
input: "<html></html>",
sourceURL: "http://mynode.com/logs/log.log",
transport: emptyHostTransport,
output: "<html></html>",
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
"no scheme": {
input: "<html></html>",
sourceURL: "http://mynode.com/logs/log.log",
transport: emptySchemeTransport,
output: "<html></html>",
contentType: "text/html",
forwardedURI: "/proxy/node/node1:10250/logs/log.log",
},
}
testItem := func(name string, item *Item) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check request headers.
if got, want := r.Header.Get("X-Forwarded-Uri"), item.forwardedURI; got != want {
t.Errorf("%v: X-Forwarded-Uri = %q, want %q", name, got, want)
}
if len(item.transport.Host) == 0 {
_, present := r.Header["X-Forwarded-Host"]
if present {
t.Errorf("%v: X-Forwarded-Host header should not be present", name)
}
} else {
if got, want := r.Header.Get("X-Forwarded-Host"), item.transport.Host; got != want {
t.Errorf("%v: X-Forwarded-Host = %q, want %q", name, got, want)
}
}
if len(item.transport.Scheme) == 0 {
_, present := r.Header["X-Forwarded-Proto"]
if present {
t.Errorf("%v: X-Forwarded-Proto header should not be present", name)
}
} else {
if got, want := r.Header.Get("X-Forwarded-Proto"), item.transport.Scheme; got != want {
t.Errorf("%v: X-Forwarded-Proto = %q, want %q", name, got, want)
}
}
// Send response.
if item.redirect != "" {
http.Redirect(w, r, item.redirect, http.StatusMovedPermanently)
return
}
w.Header().Set("Content-Type", item.contentType)
fmt.Fprint(w, item.input)
}))
defer server.Close()
// Replace source URL with our test server address.
sourceURL := parseURLOrDie(item.sourceURL)
serverURL := parseURLOrDie(server.URL)
item.input = strings.Replace(item.input, sourceURL.Host, serverURL.Host, -1)
item.redirect = strings.Replace(item.redirect, sourceURL.Host, serverURL.Host, -1)
sourceURL.Host = serverURL.Host
req, err := http.NewRequest("GET", sourceURL.String(), nil)
if err != nil {
t.Errorf("%v: Unexpected error: %v", name, err)
return
}
if item.reqHost != "" {
req.Host = item.reqHost
}
resp, err := item.transport.RoundTrip(req)
if err != nil {
t.Errorf("%v: Unexpected error: %v", name, err)
return
}
if item.redirect != "" {
// Check that redirect URLs get rewritten properly.
if got, want := resp.Header.Get("Location"), item.redirectWant; got != want {
t.Errorf("%v: Location header = %q, want %q", name, got, want)
}
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("%v: Unexpected error: %v", name, err)
return
}
if e, a := item.output, string(body); e != a {
t.Errorf("%v: expected %v, but got %v", name, e, a)
}
}
for name, item := range table {
testItem(name, &item)
}
}

View File

@ -0,0 +1,413 @@
/*
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 proxy
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/httpstream"
utilnet "k8s.io/apimachinery/pkg/util/net"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"github.com/golang/glog"
"github.com/mxk/go-flowrate/flowrate"
)
// UpgradeRequestRoundTripper provides an additional method to decorate a request
// with any authentication or other protocol level information prior to performing
// an upgrade on the server. Any response will be handled by the intercepting
// proxy.
type UpgradeRequestRoundTripper interface {
http.RoundTripper
// WrapRequest takes a valid HTTP request and returns a suitably altered version
// of request with any HTTP level values required to complete the request half of
// an upgrade on the server. It does not get a chance to see the response and
// should bypass any request side logic that expects to see the response.
WrapRequest(*http.Request) (*http.Request, error)
}
// UpgradeAwareHandler is a handler for proxy requests that may require an upgrade
type UpgradeAwareHandler struct {
// UpgradeRequired will reject non-upgrade connections if true.
UpgradeRequired bool
// Location is the location of the upstream proxy. It is used as the location to Dial on the upstream server
// for upgrade requests unless UseRequestLocationOnUpgrade is true.
Location *url.URL
// Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used
Transport http.RoundTripper
// UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided.
// This allows clients to disable HTTP/2.
UpgradeTransport UpgradeRequestRoundTripper
// WrapTransport indicates whether the provided Transport should be wrapped with default proxy transport behavior (URL rewriting, X-Forwarded-* header setting)
WrapTransport bool
// InterceptRedirects determines whether the proxy should sniff backend responses for redirects,
// following them as necessary.
InterceptRedirects bool
// UseRequestLocation will use the incoming request URL when talking to the backend server.
UseRequestLocation bool
// FlushInterval controls how often the standard HTTP proxy will flush content from the upstream.
FlushInterval time.Duration
// MaxBytesPerSec controls the maximum rate for an upstream connection. No rate is imposed if the value is zero.
MaxBytesPerSec int64
// Responder is passed errors that occur while setting up proxying.
Responder ErrorResponder
}
const defaultFlushInterval = 200 * time.Millisecond
// ErrorResponder abstracts error reporting to the proxy handler to remove the need to hardcode a particular
// error format.
type ErrorResponder interface {
Error(w http.ResponseWriter, req *http.Request, err error)
}
// SimpleErrorResponder is the legacy implementation of ErrorResponder for callers that only
// service a single request/response per proxy.
type SimpleErrorResponder interface {
Error(err error)
}
func NewErrorResponder(r SimpleErrorResponder) ErrorResponder {
return simpleResponder{r}
}
type simpleResponder struct {
responder SimpleErrorResponder
}
func (r simpleResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
r.responder.Error(err)
}
// upgradeRequestRoundTripper implements proxy.UpgradeRequestRoundTripper.
type upgradeRequestRoundTripper struct {
http.RoundTripper
upgrader http.RoundTripper
}
var (
_ UpgradeRequestRoundTripper = &upgradeRequestRoundTripper{}
_ utilnet.RoundTripperWrapper = &upgradeRequestRoundTripper{}
)
// WrappedRoundTripper returns the round tripper that a caller would use.
func (rt *upgradeRequestRoundTripper) WrappedRoundTripper() http.RoundTripper {
return rt.RoundTripper
}
// WriteToRequest calls the nested upgrader and then copies the returned request
// fields onto the passed request.
func (rt *upgradeRequestRoundTripper) WrapRequest(req *http.Request) (*http.Request, error) {
resp, err := rt.upgrader.RoundTrip(req)
if err != nil {
return nil, err
}
return resp.Request, nil
}
// onewayRoundTripper captures the provided request - which is assumed to have
// been modified by other round trippers - and then returns a fake response.
type onewayRoundTripper struct{}
// RoundTrip returns a simple 200 OK response that captures the provided request.
func (onewayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return &http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(&bytes.Buffer{}),
Request: req,
}, nil
}
// MirrorRequest is a round tripper that can be called to get back the calling request as
// the core round tripper in a chain.
var MirrorRequest http.RoundTripper = onewayRoundTripper{}
// NewUpgradeRequestRoundTripper takes two round trippers - one for the underlying TCP connection, and
// one that is able to write headers to an HTTP request. The request rt is used to set the request headers
// and that is written to the underlying connection rt.
func NewUpgradeRequestRoundTripper(connection, request http.RoundTripper) UpgradeRequestRoundTripper {
return &upgradeRequestRoundTripper{
RoundTripper: connection,
upgrader: request,
}
}
// normalizeLocation returns the result of parsing the full URL, with scheme set to http if missing
func normalizeLocation(location *url.URL) *url.URL {
normalized, _ := url.Parse(location.String())
if len(normalized.Scheme) == 0 {
normalized.Scheme = "http"
}
return normalized
}
// NewUpgradeAwareHandler creates a new proxy handler with a default flush interval. Responder is required for returning
// errors to the caller.
func NewUpgradeAwareHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder ErrorResponder) *UpgradeAwareHandler {
return &UpgradeAwareHandler{
Location: normalizeLocation(location),
Transport: transport,
WrapTransport: wrapTransport,
UpgradeRequired: upgradeRequired,
FlushInterval: defaultFlushInterval,
Responder: responder,
}
}
// ServeHTTP handles the proxy request
func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if h.tryUpgrade(w, req) {
return
}
if h.UpgradeRequired {
h.Responder.Error(w, req, errors.NewBadRequest("Upgrade request required"))
return
}
loc := *h.Location
loc.RawQuery = req.URL.RawQuery
// If original request URL ended in '/', append a '/' at the end of the
// of the proxy URL
if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") {
loc.Path += "/"
}
// From pkg/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP:
// Redirect requests with an empty path to a location that ends with a '/'
// This is essentially a hack for http://issue.k8s.io/4958.
// Note: Keep this code after tryUpgrade to not break that flow.
if len(loc.Path) == 0 {
var queryPart string
if len(req.URL.RawQuery) > 0 {
queryPart = "?" + req.URL.RawQuery
}
w.Header().Set("Location", req.URL.Path+"/"+queryPart)
w.WriteHeader(http.StatusMovedPermanently)
return
}
if h.Transport == nil || h.WrapTransport {
h.Transport = h.defaultProxyTransport(req.URL, h.Transport)
}
// WithContext creates a shallow clone of the request with the new context.
newReq := req.WithContext(context.Background())
newReq.Header = utilnet.CloneHeader(req.Header)
if !h.UseRequestLocation {
newReq.URL = &loc
}
proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host})
proxy.Transport = h.Transport
proxy.FlushInterval = h.FlushInterval
proxy.ServeHTTP(w, newReq)
}
// tryUpgrade returns true if the request was handled.
func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Request) bool {
if !httpstream.IsUpgradeRequest(req) {
glog.V(6).Infof("Request was not an upgrade")
return false
}
var (
backendConn net.Conn
rawResponse []byte
err error
)
location := *h.Location
if h.UseRequestLocation {
location = *req.URL
location.Scheme = h.Location.Scheme
location.Host = h.Location.Host
}
clone := utilnet.CloneRequest(req)
// Only append X-Forwarded-For in the upgrade path, since httputil.NewSingleHostReverseProxy
// handles this in the non-upgrade path.
utilnet.AppendForwardedForHeader(clone)
if h.InterceptRedirects {
glog.V(6).Infof("Connecting to backend proxy (intercepting redirects) %s\n Headers: %v", &location, clone.Header)
backendConn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, &location, clone.Header, req.Body, utilnet.DialerFunc(h.DialForUpgrade))
} else {
glog.V(6).Infof("Connecting to backend proxy (direct dial) %s\n Headers: %v", &location, clone.Header)
clone.URL = &location
backendConn, err = h.DialForUpgrade(clone)
}
if err != nil {
glog.V(6).Infof("Proxy connection error: %v", err)
h.Responder.Error(w, req, err)
return true
}
defer backendConn.Close()
// Once the connection is hijacked, the ErrorResponder will no longer work, so
// hijacking should be the last step in the upgrade.
requestHijacker, ok := w.(http.Hijacker)
if !ok {
glog.V(6).Infof("Unable to hijack response writer: %T", w)
h.Responder.Error(w, req, fmt.Errorf("request connection cannot be hijacked: %T", w))
return true
}
requestHijackedConn, _, err := requestHijacker.Hijack()
if err != nil {
glog.V(6).Infof("Unable to hijack response: %v", err)
h.Responder.Error(w, req, fmt.Errorf("error hijacking connection: %v", err))
return true
}
defer requestHijackedConn.Close()
// Forward raw response bytes back to client.
if len(rawResponse) > 0 {
glog.V(6).Infof("Writing %d bytes to hijacked connection", len(rawResponse))
if _, err = requestHijackedConn.Write(rawResponse); err != nil {
utilruntime.HandleError(fmt.Errorf("Error proxying response from backend to client: %v", err))
}
}
// Proxy the connection.
wg := &sync.WaitGroup{}
wg.Add(2)
go func() {
var writer io.WriteCloser
if h.MaxBytesPerSec > 0 {
writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec)
} else {
writer = backendConn
}
_, err := io.Copy(writer, requestHijackedConn)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
glog.Errorf("Error proxying data from client to backend: %v", err)
}
wg.Done()
}()
go func() {
var reader io.ReadCloser
if h.MaxBytesPerSec > 0 {
reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec)
} else {
reader = backendConn
}
_, err := io.Copy(requestHijackedConn, reader)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
glog.Errorf("Error proxying data from backend to client: %v", err)
}
wg.Done()
}()
wg.Wait()
return true
}
func (h *UpgradeAwareHandler) Dial(req *http.Request) (net.Conn, error) {
return dial(req, h.Transport)
}
func (h *UpgradeAwareHandler) DialForUpgrade(req *http.Request) (net.Conn, error) {
if h.UpgradeTransport == nil {
return dial(req, h.Transport)
}
updatedReq, err := h.UpgradeTransport.WrapRequest(req)
if err != nil {
return nil, err
}
return dial(updatedReq, h.UpgradeTransport)
}
// dial dials the backend at req.URL and writes req to it.
func dial(req *http.Request, transport http.RoundTripper) (net.Conn, error) {
conn, err := DialURL(req.URL, transport)
if err != nil {
return nil, fmt.Errorf("error dialing backend: %v", err)
}
if err = req.Write(conn); err != nil {
conn.Close()
return nil, fmt.Errorf("error sending request: %v", err)
}
return conn, err
}
var _ utilnet.Dialer = &UpgradeAwareHandler{}
func (h *UpgradeAwareHandler) defaultProxyTransport(url *url.URL, internalTransport http.RoundTripper) http.RoundTripper {
scheme := url.Scheme
host := url.Host
suffix := h.Location.Path
if strings.HasSuffix(url.Path, "/") && !strings.HasSuffix(suffix, "/") {
suffix += "/"
}
pathPrepend := strings.TrimSuffix(url.Path, suffix)
rewritingTransport := &Transport{
Scheme: scheme,
Host: host,
PathPrepend: pathPrepend,
RoundTripper: internalTransport,
}
return &corsRemovingTransport{
RoundTripper: rewritingTransport,
}
}
// corsRemovingTransport is a wrapper for an internal transport. It removes CORS headers
// from the internal response.
// Implements pkg/util/net.RoundTripperWrapper
type corsRemovingTransport struct {
http.RoundTripper
}
var _ = utilnet.RoundTripperWrapper(&corsRemovingTransport{})
func (rt *corsRemovingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := rt.RoundTripper.RoundTrip(req)
if err != nil {
return nil, err
}
removeCORSHeaders(resp)
return resp, nil
}
func (rt *corsRemovingTransport) WrappedRoundTripper() http.RoundTripper {
return rt.RoundTripper
}
// removeCORSHeaders strip CORS headers sent from the backend
// This should be called on all responses before returning
func removeCORSHeaders(resp *http.Response) {
resp.Header.Del("Access-Control-Allow-Credentials")
resp.Header.Del("Access-Control-Allow-Headers")
resp.Header.Del("Access-Control-Allow-Methods")
resp.Header.Del("Access-Control-Allow-Origin")
}

View File

@ -0,0 +1,818 @@
/*
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 proxy
import (
"bytes"
"compress/gzip"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/websocket"
"k8s.io/apimachinery/pkg/util/httpstream"
utilnet "k8s.io/apimachinery/pkg/util/net"
)
const fakeStatusCode = 567
type fakeResponder struct {
t *testing.T
called bool
err error
// called chan error
w http.ResponseWriter
}
func (r *fakeResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
if r.called {
r.t.Errorf("Error responder called again!\nprevious error: %v\nnew error: %v", r.err, err)
}
w.WriteHeader(fakeStatusCode)
_, writeErr := w.Write([]byte(err.Error()))
assert.NoError(r.t, writeErr)
r.called = true
r.err = err
}
type fakeConn struct {
err error // The error to return when io is performed over the connection.
}
func (f *fakeConn) Read([]byte) (int, error) { return 0, f.err }
func (f *fakeConn) Write([]byte) (int, error) { return 0, f.err }
func (f *fakeConn) Close() error { return nil }
func (fakeConn) LocalAddr() net.Addr { return nil }
func (fakeConn) RemoteAddr() net.Addr { return nil }
func (fakeConn) SetDeadline(t time.Time) error { return nil }
func (fakeConn) SetReadDeadline(t time.Time) error { return nil }
func (fakeConn) SetWriteDeadline(t time.Time) error { return nil }
type SimpleBackendHandler struct {
requestURL url.URL
requestHeader http.Header
requestBody []byte
requestMethod string
responseBody string
responseHeader map[string]string
t *testing.T
}
func (s *SimpleBackendHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s.requestURL = *req.URL
s.requestHeader = req.Header
s.requestMethod = req.Method
var err error
s.requestBody, err = ioutil.ReadAll(req.Body)
if err != nil {
s.t.Errorf("Unexpected error: %v", err)
return
}
if s.responseHeader != nil {
for k, v := range s.responseHeader {
w.Header().Add(k, v)
}
}
w.Write([]byte(s.responseBody))
}
func validateParameters(t *testing.T, name string, actual url.Values, expected map[string]string) {
for k, v := range expected {
actualValue, ok := actual[k]
if !ok {
t.Errorf("%s: Expected parameter %s not received", name, k)
continue
}
if actualValue[0] != v {
t.Errorf("%s: Parameter %s values don't match. Actual: %#v, Expected: %s",
name, k, actualValue, v)
}
}
}
func validateHeaders(t *testing.T, name string, actual http.Header, expected map[string]string, notExpected []string) {
for k, v := range expected {
actualValue, ok := actual[k]
if !ok {
t.Errorf("%s: Expected header %s not received", name, k)
continue
}
if actualValue[0] != v {
t.Errorf("%s: Header %s values don't match. Actual: %s, Expected: %s",
name, k, actualValue, v)
}
}
if notExpected == nil {
return
}
for _, h := range notExpected {
if _, present := actual[h]; present {
t.Errorf("%s: unexpected header: %s", name, h)
}
}
}
func TestServeHTTP(t *testing.T) {
tests := []struct {
name string
method string
requestPath string
expectedPath string
requestBody string
requestParams map[string]string
requestHeader map[string]string
responseHeader map[string]string
expectedRespHeader map[string]string
notExpectedRespHeader []string
upgradeRequired bool
expectError func(err error) bool
}{
{
name: "root path, simple get",
method: "GET",
requestPath: "/",
expectedPath: "/",
},
{
name: "no upgrade header sent",
method: "GET",
requestPath: "/",
upgradeRequired: true,
expectError: func(err error) bool {
return err != nil && strings.Contains(err.Error(), "Upgrade request required")
},
},
{
name: "simple path, get",
method: "GET",
requestPath: "/path/to/test",
expectedPath: "/path/to/test",
},
{
name: "request params",
method: "POST",
requestPath: "/some/path/",
expectedPath: "/some/path/",
requestParams: map[string]string{"param1": "value/1", "param2": "value%2"},
requestBody: "test request body",
},
{
name: "request headers",
method: "PUT",
requestPath: "/some/path",
expectedPath: "/some/path",
requestHeader: map[string]string{"Header1": "value1", "Header2": "value2"},
},
{
name: "empty path - slash should be added",
method: "GET",
requestPath: "",
expectedPath: "/",
},
{
name: "remove CORS headers",
method: "GET",
requestPath: "/some/path",
expectedPath: "/some/path",
responseHeader: map[string]string{
"Header1": "value1",
"Access-Control-Allow-Origin": "some.server",
"Access-Control-Allow-Methods": "GET"},
expectedRespHeader: map[string]string{
"Header1": "value1",
},
notExpectedRespHeader: []string{
"Access-Control-Allow-Origin",
"Access-Control-Allow-Methods",
},
},
}
for i, test := range tests {
func() {
backendResponse := "<html><head></head><body><a href=\"/test/path\">Hello</a></body></html>"
backendResponseHeader := test.responseHeader
// Test a simple header if not specified in the test
if backendResponseHeader == nil && test.expectedRespHeader == nil {
backendResponseHeader = map[string]string{"Content-Type": "text/html"}
test.expectedRespHeader = map[string]string{"Content-Type": "text/html"}
}
backendHandler := &SimpleBackendHandler{
responseBody: backendResponse,
responseHeader: backendResponseHeader,
}
backendServer := httptest.NewServer(backendHandler)
defer backendServer.Close()
responder := &fakeResponder{t: t}
backendURL, _ := url.Parse(backendServer.URL)
backendURL.Path = test.requestPath
proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, test.upgradeRequired, responder)
proxyServer := httptest.NewServer(proxyHandler)
defer proxyServer.Close()
proxyURL, _ := url.Parse(proxyServer.URL)
proxyURL.Path = test.requestPath
paramValues := url.Values{}
for k, v := range test.requestParams {
paramValues[k] = []string{v}
}
proxyURL.RawQuery = paramValues.Encode()
var requestBody io.Reader
if test.requestBody != "" {
requestBody = bytes.NewBufferString(test.requestBody)
}
req, err := http.NewRequest(test.method, proxyURL.String(), requestBody)
if test.requestHeader != nil {
header := http.Header{}
for k, v := range test.requestHeader {
header.Add(k, v)
}
req.Header = header
}
if err != nil {
t.Errorf("Error creating client request: %v", err)
}
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
t.Errorf("Error from proxy request: %v", err)
}
if test.expectError != nil {
if !responder.called {
t.Errorf("%d: responder was not invoked", i)
return
}
if !test.expectError(responder.err) {
t.Errorf("%d: unexpected error: %v", i, responder.err)
}
return
}
// Validate backend request
// Method
if backendHandler.requestMethod != test.method {
t.Errorf("Unexpected request method: %s. Expected: %s",
backendHandler.requestMethod, test.method)
}
// Body
if string(backendHandler.requestBody) != test.requestBody {
t.Errorf("Unexpected request body: %s. Expected: %s",
string(backendHandler.requestBody), test.requestBody)
}
// Path
if backendHandler.requestURL.Path != test.expectedPath {
t.Errorf("Unexpected request path: %s", backendHandler.requestURL.Path)
}
// Parameters
validateParameters(t, test.name, backendHandler.requestURL.Query(), test.requestParams)
// Headers
validateHeaders(t, test.name+" backend request", backendHandler.requestHeader,
test.requestHeader, nil)
// Validate proxy response
// Response Headers
validateHeaders(t, test.name+" backend headers", res.Header, test.expectedRespHeader, test.notExpectedRespHeader)
// Validate Body
responseBody, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("Unexpected error reading response body: %v", err)
}
if rb := string(responseBody); rb != backendResponse {
t.Errorf("Did not get expected response body: %s. Expected: %s", rb, backendResponse)
}
// Error
if responder.called {
t.Errorf("Unexpected proxy handler error: %v", responder.err)
}
}()
}
}
type RoundTripperFunc func(req *http.Request) (*http.Response, error)
func (fn RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req)
}
func TestProxyUpgrade(t *testing.T) {
localhostPool := x509.NewCertPool()
if !localhostPool.AppendCertsFromPEM(localhostCert) {
t.Errorf("error setting up localhostCert pool")
}
testcases := map[string]struct {
ServerFunc func(http.Handler) *httptest.Server
ProxyTransport http.RoundTripper
UpgradeTransport UpgradeRequestRoundTripper
ExpectedAuth string
}{
"http": {
ServerFunc: httptest.NewServer,
ProxyTransport: nil,
},
"https (invalid hostname + InsecureSkipVerify)": {
ServerFunc: func(h http.Handler) *httptest.Server {
cert, err := tls.X509KeyPair(exampleCert, exampleKey)
if err != nil {
t.Errorf("https (invalid hostname): proxy_test: %v", err)
}
ts := httptest.NewUnstartedServer(h)
ts.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
}
ts.StartTLS()
return ts
},
ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}),
},
"https (valid hostname + RootCAs)": {
ServerFunc: func(h http.Handler) *httptest.Server {
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
t.Errorf("https (valid hostname): proxy_test: %v", err)
}
ts := httptest.NewUnstartedServer(h)
ts.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
}
ts.StartTLS()
return ts
},
ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}),
},
"https (valid hostname + RootCAs + custom dialer)": {
ServerFunc: func(h http.Handler) *httptest.Server {
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
t.Errorf("https (valid hostname): proxy_test: %v", err)
}
ts := httptest.NewUnstartedServer(h)
ts.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
}
ts.StartTLS()
return ts
},
ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{Dial: net.Dial, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}),
},
"https (valid hostname + RootCAs + custom dialer + bearer token)": {
ServerFunc: func(h http.Handler) *httptest.Server {
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
t.Errorf("https (valid hostname): proxy_test: %v", err)
}
ts := httptest.NewUnstartedServer(h)
ts.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
}
ts.StartTLS()
return ts
},
ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{Dial: net.Dial, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}),
UpgradeTransport: NewUpgradeRequestRoundTripper(
utilnet.SetOldTransportDefaults(&http.Transport{Dial: net.Dial, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}),
RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
req = utilnet.CloneRequest(req)
req.Header.Set("Authorization", "Bearer 1234")
return MirrorRequest.RoundTrip(req)
}),
),
ExpectedAuth: "Bearer 1234",
},
}
for k, tc := range testcases {
for _, redirect := range []bool{false, true} {
tcName := k
backendPath := "/hello"
if redirect {
tcName += " with redirect"
backendPath = "/redirect"
}
func() { // Cleanup after each test case.
backend := http.NewServeMux()
backend.Handle("/hello", websocket.Handler(func(ws *websocket.Conn) {
if ws.Request().Header.Get("Authorization") != tc.ExpectedAuth {
t.Errorf("%s: unexpected headers on request: %v", k, ws.Request().Header)
defer ws.Close()
ws.Write([]byte("you failed"))
return
}
defer ws.Close()
body := make([]byte, 5)
ws.Read(body)
ws.Write([]byte("hello " + string(body)))
}))
backend.Handle("/redirect", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/hello", http.StatusFound)
}))
backendServer := tc.ServerFunc(backend)
defer backendServer.Close()
serverURL, _ := url.Parse(backendServer.URL)
serverURL.Path = backendPath
proxyHandler := NewUpgradeAwareHandler(serverURL, tc.ProxyTransport, false, false, &noErrorsAllowed{t: t})
proxyHandler.UpgradeTransport = tc.UpgradeTransport
proxyHandler.InterceptRedirects = redirect
proxy := httptest.NewServer(proxyHandler)
defer proxy.Close()
ws, err := websocket.Dial("ws://"+proxy.Listener.Addr().String()+"/some/path", "", "http://127.0.0.1/")
if err != nil {
t.Fatalf("%s: websocket dial err: %s", tcName, err)
}
defer ws.Close()
if _, err := ws.Write([]byte("world")); err != nil {
t.Fatalf("%s: write err: %s", tcName, err)
}
response := make([]byte, 20)
n, err := ws.Read(response)
if err != nil {
t.Fatalf("%s: read err: %s", tcName, err)
}
if e, a := "hello world", string(response[0:n]); e != a {
t.Fatalf("%s: expected '%#v', got '%#v'", tcName, e, a)
}
}()
}
}
}
type noErrorsAllowed struct {
t *testing.T
}
func (r *noErrorsAllowed) Error(w http.ResponseWriter, req *http.Request, err error) {
r.t.Error(err)
}
func TestProxyUpgradeErrorResponse(t *testing.T) {
var (
responder *fakeResponder
expectedErr = errors.New("EXPECTED")
)
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
transport := http.DefaultTransport.(*http.Transport)
transport.Dial = func(network, addr string) (net.Conn, error) {
return &fakeConn{err: expectedErr}, nil
}
responder = &fakeResponder{t: t, w: w}
proxyHandler := NewUpgradeAwareHandler(
&url.URL{
Host: "fake-backend",
},
transport,
false,
true,
responder,
)
proxyHandler.ServeHTTP(w, r)
}))
defer proxy.Close()
// Send request to proxy server.
req, err := http.NewRequest("POST", "http://"+proxy.Listener.Addr().String()+"/some/path", nil)
require.NoError(t, err)
req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// Expect error response.
assert.True(t, responder.called)
assert.Equal(t, fakeStatusCode, resp.StatusCode)
msg, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
assert.Contains(t, string(msg), expectedErr.Error())
}
func TestDefaultProxyTransport(t *testing.T) {
tests := []struct {
name,
url,
location,
expectedScheme,
expectedHost,
expectedPathPrepend string
}{
{
name: "simple path",
url: "http://test.server:8080/a/test/location",
location: "http://localhost/location",
expectedScheme: "http",
expectedHost: "test.server:8080",
expectedPathPrepend: "/a/test",
},
{
name: "empty path",
url: "http://test.server:8080/a/test/",
location: "http://localhost",
expectedScheme: "http",
expectedHost: "test.server:8080",
expectedPathPrepend: "/a/test",
},
{
name: "location ending in slash",
url: "http://test.server:8080/a/test/",
location: "http://localhost/",
expectedScheme: "http",
expectedHost: "test.server:8080",
expectedPathPrepend: "/a/test",
},
}
for _, test := range tests {
locURL, _ := url.Parse(test.location)
URL, _ := url.Parse(test.url)
h := NewUpgradeAwareHandler(locURL, nil, false, false, nil)
result := h.defaultProxyTransport(URL, nil)
transport := result.(*corsRemovingTransport).RoundTripper.(*Transport)
if transport.Scheme != test.expectedScheme {
t.Errorf("%s: unexpected scheme. Actual: %s, Expected: %s", test.name, transport.Scheme, test.expectedScheme)
}
if transport.Host != test.expectedHost {
t.Errorf("%s: unexpected host. Actual: %s, Expected: %s", test.name, transport.Host, test.expectedHost)
}
if transport.PathPrepend != test.expectedPathPrepend {
t.Errorf("%s: unexpected path prepend. Actual: %s, Expected: %s", test.name, transport.PathPrepend, test.expectedPathPrepend)
}
}
}
func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) {
chunk := func(data []byte) []byte {
out := &bytes.Buffer{}
chunker := httputil.NewChunkedWriter(out)
for _, b := range data {
if _, err := chunker.Write([]byte{b}); err != nil {
panic(err)
}
}
chunker.Close()
out.Write([]byte("\r\n"))
return out.Bytes()
}
zip := func(data []byte) []byte {
out := &bytes.Buffer{}
zipper := gzip.NewWriter(out)
if _, err := zipper.Write(data); err != nil {
panic(err)
}
zipper.Close()
return out.Bytes()
}
sampleData := []byte("abcde")
table := map[string]struct {
reqHeaders http.Header
reqBody []byte
expectedHeaders http.Header
expectedBody []byte
}{
"content-length": {
reqHeaders: http.Header{
"Content-Length": []string{"5"},
},
reqBody: sampleData,
expectedHeaders: http.Header{
"Content-Length": []string{"5"},
"Content-Encoding": nil, // none set
"Transfer-Encoding": nil, // none set
},
expectedBody: sampleData,
},
"content-length + identity transfer-encoding": {
reqHeaders: http.Header{
"Content-Length": []string{"5"},
"Transfer-Encoding": []string{"identity"},
},
reqBody: sampleData,
expectedHeaders: http.Header{
"Content-Length": []string{"5"},
"Content-Encoding": nil, // none set
"Transfer-Encoding": nil, // gets removed
},
expectedBody: sampleData,
},
"content-length + gzip content-encoding": {
reqHeaders: http.Header{
"Content-Length": []string{strconv.Itoa(len(zip(sampleData)))},
"Content-Encoding": []string{"gzip"},
},
reqBody: zip(sampleData),
expectedHeaders: http.Header{
"Content-Length": []string{strconv.Itoa(len(zip(sampleData)))},
"Content-Encoding": []string{"gzip"},
"Transfer-Encoding": nil, // none set
},
expectedBody: zip(sampleData),
},
"chunked transfer-encoding": {
reqHeaders: http.Header{
"Transfer-Encoding": []string{"chunked"},
},
reqBody: chunk(sampleData),
expectedHeaders: http.Header{
"Content-Length": nil, // none set
"Content-Encoding": nil, // none set
"Transfer-Encoding": nil, // Transfer-Encoding gets removed
},
expectedBody: sampleData, // sample data is unchunked
},
"chunked transfer-encoding + gzip content-encoding": {
reqHeaders: http.Header{
"Content-Encoding": []string{"gzip"},
"Transfer-Encoding": []string{"chunked"},
},
reqBody: chunk(zip(sampleData)),
expectedHeaders: http.Header{
"Content-Length": nil, // none set
"Content-Encoding": []string{"gzip"},
"Transfer-Encoding": nil, // gets removed
},
expectedBody: zip(sampleData), // sample data is unchunked, but content-encoding is preserved
},
// "Transfer-Encoding: gzip" is not supported by go
// See http/transfer.go#fixTransferEncoding (https://golang.org/src/net/http/transfer.go#L427)
// Once it is supported, this test case should succeed
//
// "gzip+chunked transfer-encoding": {
// reqHeaders: http.Header{
// "Transfer-Encoding": []string{"chunked,gzip"},
// },
// reqBody: chunk(zip(sampleData)),
//
// expectedHeaders: http.Header{
// "Content-Length": nil, // no content-length headers
// "Transfer-Encoding": nil, // Transfer-Encoding gets removed
// },
// expectedBody: sampleData,
// },
}
successfulResponse := "backend passed tests"
for k, item := range table {
// Start the downstream server
downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Verify headers
for header, v := range item.expectedHeaders {
if !reflect.DeepEqual(v, req.Header[header]) {
t.Errorf("%s: Expected headers for %s to be %v, got %v", k, header, v, req.Header[header])
}
}
// Read body
body, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("%s: unexpected error %v", k, err)
}
req.Body.Close()
// Verify length
if req.ContentLength > 0 && req.ContentLength != int64(len(body)) {
t.Errorf("%s: ContentLength was %d, len(data) was %d", k, req.ContentLength, len(body))
}
// Verify content
if !bytes.Equal(item.expectedBody, body) {
t.Errorf("%s: Expected %q, got %q", k, string(item.expectedBody), string(body))
}
// Write successful response
w.Write([]byte(successfulResponse))
}))
defer downstreamServer.Close()
responder := &fakeResponder{t: t}
backendURL, _ := url.Parse(downstreamServer.URL)
proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, false, responder)
proxyServer := httptest.NewServer(proxyHandler)
defer proxyServer.Close()
// Dial the proxy server
conn, err := net.Dial(proxyServer.Listener.Addr().Network(), proxyServer.Listener.Addr().String())
if err != nil {
t.Errorf("unexpected error %v", err)
continue
}
defer conn.Close()
// Add standard http 1.1 headers
if item.reqHeaders == nil {
item.reqHeaders = http.Header{}
}
item.reqHeaders.Add("Connection", "close")
item.reqHeaders.Add("Host", proxyServer.Listener.Addr().String())
// Write the request headers
if _, err := fmt.Fprint(conn, "POST / HTTP/1.1\r\n"); err != nil {
t.Fatalf("%s unexpected error %v", k, err)
}
for header, values := range item.reqHeaders {
for _, value := range values {
if _, err := fmt.Fprintf(conn, "%s: %s\r\n", header, value); err != nil {
t.Fatalf("%s: unexpected error %v", k, err)
}
}
}
// Header separator
if _, err := fmt.Fprint(conn, "\r\n"); err != nil {
t.Fatalf("%s: unexpected error %v", k, err)
}
// Body
if _, err := conn.Write(item.reqBody); err != nil {
t.Fatalf("%s: unexpected error %v", k, err)
}
// Read response
response, err := ioutil.ReadAll(conn)
if err != nil {
t.Errorf("%s: unexpected error %v", k, err)
continue
}
if !strings.HasSuffix(string(response), successfulResponse) {
t.Errorf("%s: Did not get successful response: %s", k, string(response))
continue
}
}
}
// exampleCert was generated from crypto/tls/generate_cert.go with the following command:
// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var exampleCert = []byte(`-----BEGIN CERTIFICATE-----
MIIBdzCCASGgAwIBAgIRAOVTAdPnfbS5V85mfS90TfIwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
QQCoVSqeu8TBvF+70T7Jm4340YQNhds6IxjRoifenYodAO1dnKGrcbF266DJGunh
nIjQH7B12tduhl0fLK4Ezf7/AgMBAAGjUDBOMA4GA1UdDwEB/wQEAwICpDATBgNV
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4
YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EAk1kVa5uZ/AzwYDVcS9bpM/czwjjV
xq3VeSCfmNa2uNjbFvodmCRwZOHUvipAMGCUCV6j5vMrJ8eMj8tCQ36W9A==
-----END CERTIFICATE-----`)
var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAKhVKp67xMG8X7vRPsmbjfjRhA2F2zojGNGiJ96dih0A7V2coatx
sXbroMka6eGciNAfsHXa126GXR8srgTN/v8CAwEAAQJASdzdD7vKsUwMIejGCUb1
fAnLTPfAY3lFCa+CmR89nE22dAoRDv+5RbnBsZ58BazPNJHrsVPRlfXB3OQmSQr0
SQIhANoJhs+xOJE/i8nJv0uAbzKyiD1YkvRkta0GpUOULyAVAiEAxaQus3E/SuqD
P7y5NeJnE7X6XkyC35zrsJRkz7orE8MCIHdDjsI8pjyNDeGqwUCDWE/a6DrmIDwe
emHSqMN2YvChAiEAnxLCM9NWaenOsaIoP+J1rDuvw+4499nJKVqGuVrSCRkCIEqK
4KSchPMc3x8M/uhw9oWTtKFmjA/PPh0FsWCdKrEy
-----END RSA PRIVATE KEY-----`)

33
vendor/k8s.io/apimachinery/pkg/util/rand/BUILD generated vendored Normal file
View File

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

120
vendor/k8s.io/apimachinery/pkg/util/rand/rand.go generated vendored Normal file
View File

@ -0,0 +1,120 @@
/*
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 rand provides utilities related to randomization.
package rand
import (
"math/rand"
"sync"
"time"
)
var rng = struct {
sync.Mutex
rand *rand.Rand
}{
rand: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),
}
// Intn generates an integer in range [0,max).
// By design this should panic if input is invalid, <= 0.
func Intn(max int) int {
rng.Lock()
defer rng.Unlock()
return rng.rand.Intn(max)
}
// IntnRange generates an integer in range [min,max).
// By design this should panic if input is invalid, <= 0.
func IntnRange(min, max int) int {
rng.Lock()
defer rng.Unlock()
return rng.rand.Intn(max-min) + min
}
// IntnRange generates an int64 integer in range [min,max).
// By design this should panic if input is invalid, <= 0.
func Int63nRange(min, max int64) int64 {
rng.Lock()
defer rng.Unlock()
return rng.rand.Int63n(max-min) + min
}
// Seed seeds the rng with the provided seed.
func Seed(seed int64) {
rng.Lock()
defer rng.Unlock()
rng.rand = rand.New(rand.NewSource(seed))
}
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n)
// from the default Source.
func Perm(n int) []int {
rng.Lock()
defer rng.Unlock()
return rng.rand.Perm(n)
}
const (
// We omit vowels from the set of available characters to reduce the chances
// of "bad words" being formed.
alphanums = "bcdfghjklmnpqrstvwxz2456789"
// No. of bits required to index into alphanums string.
alphanumsIdxBits = 5
// Mask used to extract last alphanumsIdxBits of an int.
alphanumsIdxMask = 1<<alphanumsIdxBits - 1
// No. of random letters we can extract from a single int63.
maxAlphanumsPerInt = 63 / alphanumsIdxBits
)
// String generates a random alphanumeric string, without vowels, which is n
// characters long. This will panic if n is less than zero.
// How the random string is created:
// - we generate random int63's
// - from each int63, we are extracting multiple random letters by bit-shifting and masking
// - if some index is out of range of alphanums we neglect it (unlikely to happen multiple times in a row)
func String(n int) string {
b := make([]byte, n)
rng.Lock()
defer rng.Unlock()
randomInt63 := rng.rand.Int63()
remaining := maxAlphanumsPerInt
for i := 0; i < n; {
if remaining == 0 {
randomInt63, remaining = rng.rand.Int63(), maxAlphanumsPerInt
}
if idx := int(randomInt63 & alphanumsIdxMask); idx < len(alphanums) {
b[i] = alphanums[idx]
i++
}
randomInt63 >>= alphanumsIdxBits
remaining--
}
return string(b)
}
// SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and
// ensures that strings generated from hash functions appear consistent throughout the API.
func SafeEncodeString(s string) string {
r := make([]byte, len(s))
for i, b := range []rune(s) {
r[i] = alphanums[(int(b) % len(alphanums))]
}
return string(r)
}

114
vendor/k8s.io/apimachinery/pkg/util/rand/rand_test.go generated vendored Normal file
View File

@ -0,0 +1,114 @@
/*
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 rand
import (
"math/rand"
"strings"
"testing"
)
const (
maxRangeTestCount = 500
testStringLength = 32
)
func TestString(t *testing.T) {
valid := "0123456789abcdefghijklmnopqrstuvwxyz"
for _, l := range []int{0, 1, 2, 10, 123} {
s := String(l)
if len(s) != l {
t.Errorf("expected string of size %d, got %q", l, s)
}
for _, c := range s {
if !strings.ContainsRune(valid, c) {
t.Errorf("expected valid charaters, got %v", c)
}
}
}
}
// Confirm that panic occurs on invalid input.
func TestRangePanic(t *testing.T) {
defer func() {
if err := recover(); err == nil {
t.Errorf("Panic didn't occur!")
}
}()
// Should result in an error...
Intn(0)
}
func TestIntn(t *testing.T) {
// 0 is invalid.
for _, max := range []int{1, 2, 10, 123} {
inrange := Intn(max)
if inrange < 0 || inrange > max {
t.Errorf("%v out of range (0,%v)", inrange, max)
}
}
}
func TestPerm(t *testing.T) {
Seed(5)
rand.Seed(5)
for i := 1; i < 20; i++ {
actual := Perm(i)
expected := rand.Perm(i)
for j := 0; j < i; j++ {
if actual[j] != expected[j] {
t.Errorf("Perm call result is unexpected")
}
}
}
}
func TestIntnRange(t *testing.T) {
// 0 is invalid.
for min, max := range map[int]int{1: 2, 10: 123, 100: 500} {
for i := 0; i < maxRangeTestCount; i++ {
inrange := IntnRange(min, max)
if inrange < min || inrange >= max {
t.Errorf("%v out of range (%v,%v)", inrange, min, max)
}
}
}
}
func TestInt63nRange(t *testing.T) {
// 0 is invalid.
for min, max := range map[int64]int64{1: 2, 10: 123, 100: 500} {
for i := 0; i < maxRangeTestCount; i++ {
inrange := Int63nRange(min, max)
if inrange < min || inrange >= max {
t.Errorf("%v out of range (%v,%v)", inrange, min, max)
}
}
}
}
func BenchmarkRandomStringGeneration(b *testing.B) {
b.ResetTimer()
var s string
for i := 0; i < b.N; i++ {
s = String(testStringLength)
}
b.StopTimer()
if len(s) == 0 {
b.Fatal(s)
}
}

View File

@ -0,0 +1,26 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["constants.go"],
importpath = "k8s.io/apimachinery/pkg/util/remotecommand",
deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1: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

@ -0,0 +1,53 @@
/*
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 remotecommand
import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
DefaultStreamCreationTimeout = 30 * time.Second
// The SPDY subprotocol "channel.k8s.io" is used for remote command
// attachment/execution. This represents the initial unversioned subprotocol,
// which has the known bugs http://issues.k8s.io/13394 and
// http://issues.k8s.io/13395.
StreamProtocolV1Name = "channel.k8s.io"
// The SPDY subprotocol "v2.channel.k8s.io" is used for remote command
// attachment/execution. It is the second version of the subprotocol and
// resolves the issues present in the first version.
StreamProtocolV2Name = "v2.channel.k8s.io"
// The SPDY subprotocol "v3.channel.k8s.io" is used for remote command
// attachment/execution. It is the third version of the subprotocol and
// adds support for resizing container terminals.
StreamProtocolV3Name = "v3.channel.k8s.io"
// The SPDY subprotocol "v4.channel.k8s.io" is used for remote command
// attachment/execution. It is the 4th version of the subprotocol and
// adds support for exit codes.
StreamProtocolV4Name = "v4.channel.k8s.io"
NonZeroExitCodeReason = metav1.StatusReason("NonZeroExitCode")
ExitCodeCauseType = metav1.CauseType("ExitCode")
)
var SupportedStreamingProtocols = []string{StreamProtocolV4Name, StreamProtocolV3Name, StreamProtocolV2Name, StreamProtocolV1Name}

34
vendor/k8s.io/apimachinery/pkg/util/runtime/BUILD generated vendored Normal file
View File

@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["runtime_test.go"],
importpath = "k8s.io/apimachinery/pkg/util/runtime",
library = ":go_default_library",
)
go_library(
name = "go_default_library",
srcs = ["runtime.go"],
importpath = "k8s.io/apimachinery/pkg/util/runtime",
deps = ["//vendor/github.com/golang/glog:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

163
vendor/k8s.io/apimachinery/pkg/util/runtime/runtime.go generated vendored Normal file
View File

@ -0,0 +1,163 @@
/*
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 runtime
import (
"fmt"
"runtime"
"sync"
"time"
"github.com/golang/glog"
)
var (
// ReallyCrash controls the behavior of HandleCrash and now defaults
// true. It's still exposed so components can optionally set to false
// to restore prior behavior.
ReallyCrash = true
)
// PanicHandlers is a list of functions which will be invoked when a panic happens.
var PanicHandlers = []func(interface{}){logPanic}
// HandleCrash simply catches a crash and logs an error. Meant to be called via
// defer. Additional context-specific handlers can be provided, and will be
// called in case of panic. HandleCrash actually crashes, after calling the
// handlers and logging the panic message.
//
// TODO: remove this function. We are switching to a world where it's safe for
// apiserver to panic, since it will be restarted by kubelet. At the beginning
// of the Kubernetes project, nothing was going to restart apiserver and so
// catching panics was important. But it's actually much simpler for montoring
// software if we just exit when an unexpected panic happens.
func HandleCrash(additionalHandlers ...func(interface{})) {
if r := recover(); r != nil {
for _, fn := range PanicHandlers {
fn(r)
}
for _, fn := range additionalHandlers {
fn(r)
}
if ReallyCrash {
// Actually proceed to panic.
panic(r)
}
}
}
// logPanic logs the caller tree when a panic occurs.
func logPanic(r interface{}) {
callers := getCallers(r)
glog.Errorf("Observed a panic: %#v (%v)\n%v", r, r, callers)
}
func getCallers(r interface{}) string {
callers := ""
for i := 0; true; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
callers = callers + fmt.Sprintf("%v:%v\n", file, line)
}
return callers
}
// ErrorHandlers is a list of functions which will be invoked when an unreturnable
// error occurs.
// TODO(lavalamp): for testability, this and the below HandleError function
// should be packaged up into a testable and reusable object.
var ErrorHandlers = []func(error){
logError,
(&rudimentaryErrorBackoff{
lastErrorTime: time.Now(),
// 1ms was the number folks were able to stomach as a global rate limit.
// If you need to log errors more than 1000 times a second you
// should probably consider fixing your code instead. :)
minPeriod: time.Millisecond,
}).OnError,
}
// HandlerError is a method to invoke when a non-user facing piece of code cannot
// return an error and needs to indicate it has been ignored. Invoking this method
// is preferable to logging the error - the default behavior is to log but the
// errors may be sent to a remote server for analysis.
func HandleError(err error) {
// this is sometimes called with a nil error. We probably shouldn't fail and should do nothing instead
if err == nil {
return
}
for _, fn := range ErrorHandlers {
fn(err)
}
}
// logError prints an error with the call stack of the location it was reported
func logError(err error) {
glog.ErrorDepth(2, err)
}
type rudimentaryErrorBackoff struct {
minPeriod time.Duration // immutable
// TODO(lavalamp): use the clock for testability. Need to move that
// package for that to be accessible here.
lastErrorTimeLock sync.Mutex
lastErrorTime time.Time
}
// OnError will block if it is called more often than the embedded period time.
// This will prevent overly tight hot error loops.
func (r *rudimentaryErrorBackoff) OnError(error) {
r.lastErrorTimeLock.Lock()
defer r.lastErrorTimeLock.Unlock()
d := time.Since(r.lastErrorTime)
if d < r.minPeriod && d >= 0 {
// If the time moves backwards for any reason, do nothing
// TODO: remove check "d >= 0" after go 1.8 is no longer supported
time.Sleep(r.minPeriod - d)
}
r.lastErrorTime = time.Now()
}
// GetCaller returns the caller of the function that calls it.
func GetCaller() string {
var pc [1]uintptr
runtime.Callers(3, pc[:])
f := runtime.FuncForPC(pc[0])
if f == nil {
return fmt.Sprintf("Unable to find caller")
}
return f.Name()
}
// RecoverFromPanic replaces the specified error with an error containing the
// original error, and the call tree when a panic occurs. This enables error
// handlers to handle errors and panics the same way.
func RecoverFromPanic(err *error) {
if r := recover(); r != nil {
callers := getCallers(r)
*err = fmt.Errorf(
"recovered from panic %q. (err=%v) Call stack:\n%v",
r,
*err,
callers)
}
}

View File

@ -0,0 +1,71 @@
/*
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 runtime
import (
"fmt"
"testing"
)
func TestHandleCrash(t *testing.T) {
defer func() {
if x := recover(); x == nil {
t.Errorf("Expected a panic to recover from")
}
}()
defer HandleCrash()
panic("Test Panic")
}
func TestCustomHandleCrash(t *testing.T) {
old := PanicHandlers
defer func() { PanicHandlers = old }()
var result interface{}
PanicHandlers = []func(interface{}){
func(r interface{}) {
result = r
},
}
func() {
defer func() {
if x := recover(); x == nil {
t.Errorf("Expected a panic to recover from")
}
}()
defer HandleCrash()
panic("test")
}()
if result != "test" {
t.Errorf("did not receive custom handler")
}
}
func TestCustomHandleError(t *testing.T) {
old := ErrorHandlers
defer func() { ErrorHandlers = old }()
var result error
ErrorHandlers = []func(error){
func(err error) {
result = err
},
}
err := fmt.Errorf("test")
HandleError(err)
if result != err {
t.Errorf("did not receive custom handler")
}
}

72
vendor/k8s.io/apimachinery/pkg/util/sets/BUILD generated vendored Normal file
View File

@ -0,0 +1,72 @@
package(default_visibility = ["//visibility:public"])
load("@io_kubernetes_build//defs:go.bzl", "go_genrule")
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"byte.go",
"doc.go",
"empty.go",
"int.go",
"int64.go",
"string.go",
],
importpath = "k8s.io/apimachinery/pkg/util/sets",
)
go_genrule(
name = "set-gen",
srcs = [
"//hack/boilerplate:boilerplate.go.txt",
],
outs = [
"byte.go",
"doc.go",
"empty.go",
"int.go",
"int64.go",
"string.go",
],
cmd = """
$(location //vendor/k8s.io/code-generator/cmd/set-gen) \
--input-dirs ./vendor/k8s.io/apimachinery/pkg/util/sets/types \
--output-base $$(dirname $$(dirname $(location :byte.go))) \
--go-header-file $(location //hack/boilerplate:boilerplate.go.txt) \
--output-package sets
""",
go_deps = [
"//vendor/k8s.io/apimachinery/pkg/util/sets/types:go_default_library",
],
tools = [
"//vendor/k8s.io/code-generator/cmd/set-gen",
],
)
go_test(
name = "go_default_test",
srcs = ["set_test.go"],
importpath = "k8s.io/apimachinery/pkg/util/sets",
library = ":go_default_library",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/sets/types:all-srcs",
],
tags = ["automanaged"],
)

203
vendor/k8s.io/apimachinery/pkg/util/sets/byte.go generated vendored Normal file
View File

@ -0,0 +1,203 @@
/*
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.
*/
// This file was autogenerated by set-gen. Do not edit it manually!
package sets
import (
"reflect"
"sort"
)
// sets.Byte is a set of bytes, implemented via map[byte]struct{} for minimal memory consumption.
type Byte map[byte]Empty
// New creates a Byte from a list of values.
func NewByte(items ...byte) Byte {
ss := Byte{}
ss.Insert(items...)
return ss
}
// ByteKeySet creates a Byte from a keys of a map[byte](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func ByteKeySet(theMap interface{}) Byte {
v := reflect.ValueOf(theMap)
ret := Byte{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(byte))
}
return ret
}
// Insert adds items to the set.
func (s Byte) Insert(items ...byte) {
for _, item := range items {
s[item] = Empty{}
}
}
// Delete removes all items from the set.
func (s Byte) Delete(items ...byte) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true if and only if item is contained in the set.
func (s Byte) Has(item byte) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s Byte) HasAll(items ...byte) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s Byte) HasAny(items ...byte) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s Byte) Difference(s2 Byte) Byte {
result := NewByte()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 Byte) Union(s2 Byte) Byte {
result := NewByte()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 Byte) Intersection(s2 Byte) Byte {
var walk, other Byte
result := NewByte()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 Byte) IsSuperset(s2 Byte) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 Byte) Equal(s2 Byte) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfByte []byte
func (s sortableSliceOfByte) Len() int { return len(s) }
func (s sortableSliceOfByte) Less(i, j int) bool { return lessByte(s[i], s[j]) }
func (s sortableSliceOfByte) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted byte slice.
func (s Byte) List() []byte {
res := make(sortableSliceOfByte, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []byte(res)
}
// UnsortedList returns the slice with contents in random order.
func (s Byte) UnsortedList() []byte {
res := make([]byte, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s Byte) PopAny() (byte, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue byte
return zeroValue, false
}
// Len returns the size of the set.
func (s Byte) Len() int {
return len(s)
}
func lessByte(lhs, rhs byte) bool {
return lhs < rhs
}

20
vendor/k8s.io/apimachinery/pkg/util/sets/doc.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
/*
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.
*/
// This file was autogenerated by set-gen. Do not edit it manually!
// Package sets has auto-generated set types.
package sets

23
vendor/k8s.io/apimachinery/pkg/util/sets/empty.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
/*
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.
*/
// This file was autogenerated by set-gen. Do not edit it manually!
package sets
// Empty is public since it is used by some internal API objects for conversions between external
// string arrays and internal sets, and conversion logic requires public types today.
type Empty struct{}

203
vendor/k8s.io/apimachinery/pkg/util/sets/int.go generated vendored Normal file
View File

@ -0,0 +1,203 @@
/*
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.
*/
// This file was autogenerated by set-gen. Do not edit it manually!
package sets
import (
"reflect"
"sort"
)
// sets.Int is a set of ints, implemented via map[int]struct{} for minimal memory consumption.
type Int map[int]Empty
// New creates a Int from a list of values.
func NewInt(items ...int) Int {
ss := Int{}
ss.Insert(items...)
return ss
}
// IntKeySet creates a Int from a keys of a map[int](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func IntKeySet(theMap interface{}) Int {
v := reflect.ValueOf(theMap)
ret := Int{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(int))
}
return ret
}
// Insert adds items to the set.
func (s Int) Insert(items ...int) {
for _, item := range items {
s[item] = Empty{}
}
}
// Delete removes all items from the set.
func (s Int) Delete(items ...int) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true if and only if item is contained in the set.
func (s Int) Has(item int) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s Int) HasAll(items ...int) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s Int) HasAny(items ...int) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s Int) Difference(s2 Int) Int {
result := NewInt()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 Int) Union(s2 Int) Int {
result := NewInt()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 Int) Intersection(s2 Int) Int {
var walk, other Int
result := NewInt()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 Int) IsSuperset(s2 Int) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 Int) Equal(s2 Int) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfInt []int
func (s sortableSliceOfInt) Len() int { return len(s) }
func (s sortableSliceOfInt) Less(i, j int) bool { return lessInt(s[i], s[j]) }
func (s sortableSliceOfInt) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted int slice.
func (s Int) List() []int {
res := make(sortableSliceOfInt, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []int(res)
}
// UnsortedList returns the slice with contents in random order.
func (s Int) UnsortedList() []int {
res := make([]int, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s Int) PopAny() (int, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue int
return zeroValue, false
}
// Len returns the size of the set.
func (s Int) Len() int {
return len(s)
}
func lessInt(lhs, rhs int) bool {
return lhs < rhs
}

203
vendor/k8s.io/apimachinery/pkg/util/sets/int64.go generated vendored Normal file
View File

@ -0,0 +1,203 @@
/*
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.
*/
// This file was autogenerated by set-gen. Do not edit it manually!
package sets
import (
"reflect"
"sort"
)
// sets.Int64 is a set of int64s, implemented via map[int64]struct{} for minimal memory consumption.
type Int64 map[int64]Empty
// New creates a Int64 from a list of values.
func NewInt64(items ...int64) Int64 {
ss := Int64{}
ss.Insert(items...)
return ss
}
// Int64KeySet creates a Int64 from a keys of a map[int64](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func Int64KeySet(theMap interface{}) Int64 {
v := reflect.ValueOf(theMap)
ret := Int64{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(int64))
}
return ret
}
// Insert adds items to the set.
func (s Int64) Insert(items ...int64) {
for _, item := range items {
s[item] = Empty{}
}
}
// Delete removes all items from the set.
func (s Int64) Delete(items ...int64) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true if and only if item is contained in the set.
func (s Int64) Has(item int64) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s Int64) HasAll(items ...int64) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s Int64) HasAny(items ...int64) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s Int64) Difference(s2 Int64) Int64 {
result := NewInt64()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 Int64) Union(s2 Int64) Int64 {
result := NewInt64()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 Int64) Intersection(s2 Int64) Int64 {
var walk, other Int64
result := NewInt64()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 Int64) IsSuperset(s2 Int64) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 Int64) Equal(s2 Int64) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfInt64 []int64
func (s sortableSliceOfInt64) Len() int { return len(s) }
func (s sortableSliceOfInt64) Less(i, j int) bool { return lessInt64(s[i], s[j]) }
func (s sortableSliceOfInt64) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted int64 slice.
func (s Int64) List() []int64 {
res := make(sortableSliceOfInt64, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []int64(res)
}
// UnsortedList returns the slice with contents in random order.
func (s Int64) UnsortedList() []int64 {
res := make([]int64, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s Int64) PopAny() (int64, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue int64
return zeroValue, false
}
// Len returns the size of the set.
func (s Int64) Len() int {
return len(s)
}
func lessInt64(lhs, rhs int64) bool {
return lhs < rhs
}

270
vendor/k8s.io/apimachinery/pkg/util/sets/set_test.go generated vendored Normal file
View File

@ -0,0 +1,270 @@
/*
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 sets
import (
"reflect"
"testing"
)
func TestStringSet(t *testing.T) {
s := String{}
s2 := String{}
if len(s) != 0 {
t.Errorf("Expected len=0: %d", len(s))
}
s.Insert("a", "b")
if len(s) != 2 {
t.Errorf("Expected len=2: %d", len(s))
}
s.Insert("c")
if s.Has("d") {
t.Errorf("Unexpected contents: %#v", s)
}
if !s.Has("a") {
t.Errorf("Missing contents: %#v", s)
}
s.Delete("a")
if s.Has("a") {
t.Errorf("Unexpected contents: %#v", s)
}
s.Insert("a")
if s.HasAll("a", "b", "d") {
t.Errorf("Unexpected contents: %#v", s)
}
if !s.HasAll("a", "b") {
t.Errorf("Missing contents: %#v", s)
}
s2.Insert("a", "b", "d")
if s.IsSuperset(s2) {
t.Errorf("Unexpected contents: %#v", s)
}
s2.Delete("d")
if !s.IsSuperset(s2) {
t.Errorf("Missing contents: %#v", s)
}
}
func TestStringSetDeleteMultiples(t *testing.T) {
s := String{}
s.Insert("a", "b", "c")
if len(s) != 3 {
t.Errorf("Expected len=3: %d", len(s))
}
s.Delete("a", "c")
if len(s) != 1 {
t.Errorf("Expected len=1: %d", len(s))
}
if s.Has("a") {
t.Errorf("Unexpected contents: %#v", s)
}
if s.Has("c") {
t.Errorf("Unexpected contents: %#v", s)
}
if !s.Has("b") {
t.Errorf("Missing contents: %#v", s)
}
}
func TestNewStringSet(t *testing.T) {
s := NewString("a", "b", "c")
if len(s) != 3 {
t.Errorf("Expected len=3: %d", len(s))
}
if !s.Has("a") || !s.Has("b") || !s.Has("c") {
t.Errorf("Unexpected contents: %#v", s)
}
}
func TestStringSetList(t *testing.T) {
s := NewString("z", "y", "x", "a")
if !reflect.DeepEqual(s.List(), []string{"a", "x", "y", "z"}) {
t.Errorf("List gave unexpected result: %#v", s.List())
}
}
func TestStringSetDifference(t *testing.T) {
a := NewString("1", "2", "3")
b := NewString("1", "2", "4", "5")
c := a.Difference(b)
d := b.Difference(a)
if len(c) != 1 {
t.Errorf("Expected len=1: %d", len(c))
}
if !c.Has("3") {
t.Errorf("Unexpected contents: %#v", c.List())
}
if len(d) != 2 {
t.Errorf("Expected len=2: %d", len(d))
}
if !d.Has("4") || !d.Has("5") {
t.Errorf("Unexpected contents: %#v", d.List())
}
}
func TestStringSetHasAny(t *testing.T) {
a := NewString("1", "2", "3")
if !a.HasAny("1", "4") {
t.Errorf("expected true, got false")
}
if a.HasAny("0", "4") {
t.Errorf("expected false, got true")
}
}
func TestStringSetEquals(t *testing.T) {
// Simple case (order doesn't matter)
a := NewString("1", "2")
b := NewString("2", "1")
if !a.Equal(b) {
t.Errorf("Expected to be equal: %v vs %v", a, b)
}
// It is a set; duplicates are ignored
b = NewString("2", "2", "1")
if !a.Equal(b) {
t.Errorf("Expected to be equal: %v vs %v", a, b)
}
// Edge cases around empty sets / empty strings
a = NewString()
b = NewString()
if !a.Equal(b) {
t.Errorf("Expected to be equal: %v vs %v", a, b)
}
b = NewString("1", "2", "3")
if a.Equal(b) {
t.Errorf("Expected to be not-equal: %v vs %v", a, b)
}
b = NewString("1", "2", "")
if a.Equal(b) {
t.Errorf("Expected to be not-equal: %v vs %v", a, b)
}
// Check for equality after mutation
a = NewString()
a.Insert("1")
if a.Equal(b) {
t.Errorf("Expected to be not-equal: %v vs %v", a, b)
}
a.Insert("2")
if a.Equal(b) {
t.Errorf("Expected to be not-equal: %v vs %v", a, b)
}
a.Insert("")
if !a.Equal(b) {
t.Errorf("Expected to be equal: %v vs %v", a, b)
}
a.Delete("")
if a.Equal(b) {
t.Errorf("Expected to be not-equal: %v vs %v", a, b)
}
}
func TestStringUnion(t *testing.T) {
tests := []struct {
s1 String
s2 String
expected String
}{
{
NewString("1", "2", "3", "4"),
NewString("3", "4", "5", "6"),
NewString("1", "2", "3", "4", "5", "6"),
},
{
NewString("1", "2", "3", "4"),
NewString(),
NewString("1", "2", "3", "4"),
},
{
NewString(),
NewString("1", "2", "3", "4"),
NewString("1", "2", "3", "4"),
},
{
NewString(),
NewString(),
NewString(),
},
}
for _, test := range tests {
union := test.s1.Union(test.s2)
if union.Len() != test.expected.Len() {
t.Errorf("Expected union.Len()=%d but got %d", test.expected.Len(), union.Len())
}
if !union.Equal(test.expected) {
t.Errorf("Expected union.Equal(expected) but not true. union:%v expected:%v", union.List(), test.expected.List())
}
}
}
func TestStringIntersection(t *testing.T) {
tests := []struct {
s1 String
s2 String
expected String
}{
{
NewString("1", "2", "3", "4"),
NewString("3", "4", "5", "6"),
NewString("3", "4"),
},
{
NewString("1", "2", "3", "4"),
NewString("1", "2", "3", "4"),
NewString("1", "2", "3", "4"),
},
{
NewString("1", "2", "3", "4"),
NewString(),
NewString(),
},
{
NewString(),
NewString("1", "2", "3", "4"),
NewString(),
},
{
NewString(),
NewString(),
NewString(),
},
}
for _, test := range tests {
intersection := test.s1.Intersection(test.s2)
if intersection.Len() != test.expected.Len() {
t.Errorf("Expected intersection.Len()=%d but got %d", test.expected.Len(), intersection.Len())
}
if !intersection.Equal(test.expected) {
t.Errorf("Expected intersection.Equal(expected) but not true. intersection:%v expected:%v", intersection.List(), test.expected.List())
}
}
}

203
vendor/k8s.io/apimachinery/pkg/util/sets/string.go generated vendored Normal file
View File

@ -0,0 +1,203 @@
/*
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.
*/
// This file was autogenerated by set-gen. Do not edit it manually!
package sets
import (
"reflect"
"sort"
)
// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption.
type String map[string]Empty
// New creates a String from a list of values.
func NewString(items ...string) String {
ss := String{}
ss.Insert(items...)
return ss
}
// StringKeySet creates a String from a keys of a map[string](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func StringKeySet(theMap interface{}) String {
v := reflect.ValueOf(theMap)
ret := String{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(string))
}
return ret
}
// Insert adds items to the set.
func (s String) Insert(items ...string) {
for _, item := range items {
s[item] = Empty{}
}
}
// Delete removes all items from the set.
func (s String) Delete(items ...string) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true if and only if item is contained in the set.
func (s String) Has(item string) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s String) HasAll(items ...string) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s String) HasAny(items ...string) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s String) Difference(s2 String) String {
result := NewString()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 String) Union(s2 String) String {
result := NewString()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 String) Intersection(s2 String) String {
var walk, other String
result := NewString()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 String) IsSuperset(s2 String) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 String) Equal(s2 String) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfString []string
func (s sortableSliceOfString) Len() int { return len(s) }
func (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) }
func (s sortableSliceOfString) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted string slice.
func (s String) List() []string {
res := make(sortableSliceOfString, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []string(res)
}
// UnsortedList returns the slice with contents in random order.
func (s String) UnsortedList() []string {
res := make([]string, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s String) PopAny() (string, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue string
return zeroValue, false
}
// Len returns the size of the set.
func (s String) Len() int {
return len(s)
}
func lessString(lhs, rhs string) bool {
return lhs < rhs
}

25
vendor/k8s.io/apimachinery/pkg/util/sets/types/BUILD generated vendored Normal file
View File

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

View File

@ -0,0 +1,32 @@
/*
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 types just provides input types to the set generator. It also
// contains a "go generate" block.
// (You must first `go install k8s.io/code-generator/cmd/set-gen`)
package types
//go:generate set-gen -i k8s.io/kubernetes/pkg/util/sets/types
type ReferenceSetTypes struct {
// These types all cause files to be generated.
// These types should be reflected in the output of
// the "//pkg/util/sets:set-gen" genrule.
a int64
b int
c byte
d string
}

View File

@ -0,0 +1,61 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["patch_test.go"],
data = [
"testdata/swagger-merge-item.json",
"testdata/swagger-precision-item.json",
],
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch",
library = ":go_default_library",
deps = [
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"errors.go",
"meta.go",
"patch.go",
"types.go",
],
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch",
deps = [
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/third_party/forked/golang/json:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,5 @@
approvers:
- pwittrock
reviewers:
- mengqiy
- apelisse

View File

@ -0,0 +1,49 @@
/*
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 strategicpatch
import (
"fmt"
)
type LookupPatchMetaError struct {
Path string
Err error
}
func (e LookupPatchMetaError) Error() string {
return fmt.Sprintf("LookupPatchMetaError(%s): %v", e.Path, e.Err)
}
type FieldNotFoundError struct {
Path string
Field string
}
func (e FieldNotFoundError) Error() string {
return fmt.Sprintf("unable to find api field %q in %s", e.Field, e.Path)
}
type InvalidTypeError struct {
Path string
Expected string
Actual string
}
func (e InvalidTypeError) Error() string {
return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected)
}

View File

@ -0,0 +1,194 @@
/*
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 strategicpatch
import (
"errors"
"fmt"
"reflect"
"k8s.io/apimachinery/pkg/util/mergepatch"
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
openapi "k8s.io/kube-openapi/pkg/util/proto"
)
type PatchMeta struct {
patchStrategies []string
patchMergeKey string
}
func (pm PatchMeta) GetPatchStrategies() []string {
if pm.patchStrategies == nil {
return []string{}
}
return pm.patchStrategies
}
func (pm PatchMeta) SetPatchStrategies(ps []string) {
pm.patchStrategies = ps
}
func (pm PatchMeta) GetPatchMergeKey() string {
return pm.patchMergeKey
}
func (pm PatchMeta) SetPatchMergeKey(pmk string) {
pm.patchMergeKey = pmk
}
type LookupPatchMeta interface {
// LookupPatchMetadataForStruct gets subschema and the patch metadata (e.g. patch strategy and merge key) for map.
LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error)
// LookupPatchMetadataForSlice get subschema and the patch metadata for slice.
LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error)
// Get the type name of the field
Name() string
}
type PatchMetaFromStruct struct {
T reflect.Type
}
func NewPatchMetaFromStruct(dataStruct interface{}) (PatchMetaFromStruct, error) {
t, err := getTagStructType(dataStruct)
return PatchMetaFromStruct{T: t}, err
}
var _ LookupPatchMeta = PatchMetaFromStruct{}
func (s PatchMetaFromStruct) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadataForStruct(s.T, key)
if err != nil {
return nil, PatchMeta{}, err
}
return PatchMetaFromStruct{T: fieldType},
PatchMeta{
patchStrategies: fieldPatchStrategies,
patchMergeKey: fieldPatchMergeKey,
}, nil
}
func (s PatchMetaFromStruct) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
subschema, patchMeta, err := s.LookupPatchMetadataForStruct(key)
if err != nil {
return nil, PatchMeta{}, err
}
elemPatchMetaFromStruct := subschema.(PatchMetaFromStruct)
t := elemPatchMetaFromStruct.T
var elemType reflect.Type
switch t.Kind() {
// If t is an array or a slice, get the element type.
// If element is still an array or a slice, return an error.
// Otherwise, return element type.
case reflect.Array, reflect.Slice:
elemType = t.Elem()
if elemType.Kind() == reflect.Array || elemType.Kind() == reflect.Slice {
return nil, PatchMeta{}, errors.New("unexpected slice of slice")
}
// If t is an pointer, get the underlying element.
// If the underlying element is neither an array nor a slice, the pointer is pointing to a slice,
// e.g. https://github.com/kubernetes/kubernetes/blob/bc22e206c79282487ea0bf5696d5ccec7e839a76/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go#L2782-L2822
// If the underlying element is either an array or a slice, return its element type.
case reflect.Ptr:
t = t.Elem()
if t.Kind() == reflect.Array || t.Kind() == reflect.Slice {
t = t.Elem()
}
elemType = t
default:
return nil, PatchMeta{}, fmt.Errorf("expected slice or array type, but got: %s", s.T.Kind().String())
}
return PatchMetaFromStruct{T: elemType}, patchMeta, nil
}
func (s PatchMetaFromStruct) Name() string {
return s.T.Kind().String()
}
func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
if dataStruct == nil {
return nil, mergepatch.ErrBadArgKind(struct{}{}, nil)
}
t := reflect.TypeOf(dataStruct)
// Get the underlying type for pointers
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct)
}
return t, nil
}
func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type {
t, err := getTagStructType(dataStruct)
if err != nil {
panic(err)
}
return t
}
type PatchMetaFromOpenAPI struct {
Schema openapi.Schema
}
func NewPatchMetaFromOpenAPI(s openapi.Schema) PatchMetaFromOpenAPI {
return PatchMetaFromOpenAPI{Schema: s}
}
var _ LookupPatchMeta = PatchMetaFromOpenAPI{}
func (s PatchMetaFromOpenAPI) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
if s.Schema == nil {
return nil, PatchMeta{}, nil
}
kindItem := NewKindItem(key, s.Schema.GetPath())
s.Schema.Accept(kindItem)
err := kindItem.Error()
if err != nil {
return nil, PatchMeta{}, err
}
return PatchMetaFromOpenAPI{Schema: kindItem.subschema},
kindItem.patchmeta, nil
}
func (s PatchMetaFromOpenAPI) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
if s.Schema == nil {
return nil, PatchMeta{}, nil
}
sliceItem := NewSliceItem(key, s.Schema.GetPath())
s.Schema.Accept(sliceItem)
err := sliceItem.Error()
if err != nil {
return nil, PatchMeta{}, err
}
return PatchMetaFromOpenAPI{Schema: sliceItem.subschema},
sliceItem.patchmeta, nil
}
func (s PatchMetaFromOpenAPI) Name() string {
schema := s.Schema
return schema.GetName()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,170 @@
{
"swagger": "2.0",
"info": {
"title": "StrategicMergePatchTestingMergeItem",
"version": "v1.9.0"
},
"paths": {},
"definitions": {
"mergeItem": {
"description": "MergeItem is type definition for testing strategic merge.",
"required": [],
"properties": {
"name": {
"description": "Name field.",
"type": "string"
},
"value": {
"description": "Value field.",
"type": "string"
},
"other": {
"description": "Other field.",
"type": "string"
},
"mergingList": {
"description": "MergingList field.",
"type": "array",
"items": {
"$ref": "#/definitions/mergeItem"
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"nonMergingList": {
"description": "NonMergingList field.",
"type": "array",
"items": {
"$ref": "#/definitions/mergeItem"
}
},
"mergingIntList": {
"description": "MergingIntList field.",
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"x-kubernetes-patch-strategy": "merge"
},
"nonMergingIntList": {
"description": "NonMergingIntList field.",
"type": "array",
"items": {
"type": "integer",
"format": "int32"
}
},
"mergeItemPtr": {
"description": "MergeItemPtr field.",
"$ref": "#/definitions/mergeItem",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"simpleMap": {
"description": "SimpleMap field.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"replacingItem": {
"description": "ReplacingItem field.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension",
"x-kubernetes-patch-strategy": "replace"
},
"retainKeysMap": {
"description": "RetainKeysMap field.",
"$ref": "#/definitions/retainKeysMergeItem",
"x-kubernetes-patch-strategy": "retainKeys"
},
"retainKeysMergingList": {
"description": "RetainKeysMergingList field.",
"type": "array",
"items": {
"$ref": "#/definitions/mergeItem"
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge,retainKeys"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "fake-group",
"kind": "mergeItem",
"version": "some-version"
}
]
},
"retainKeysMergeItem": {
"description": "RetainKeysMergeItem is type definition for testing strategic merge.",
"required": [],
"properties": {
"name": {
"description": "Name field.",
"type": "string"
},
"value": {
"description": "Value field.",
"type": "string"
},
"other": {
"description": "Other field.",
"type": "string"
},
"simpleMap": {
"description": "SimpleMap field.",
"additionalProperties": "object",
"items": {
"type": "string"
}
},
"mergingList": {
"description": "MergingList field.",
"type": "array",
"items": {
"$ref": "#/definitions/mergeItem"
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"nonMergingList": {
"description": "NonMergingList field.",
"type": "array",
"items": {
"$ref": "#/definitions/mergeItem"
}
},
"mergingIntList": {
"description": "MergingIntList field.",
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"x-kubernetes-patch-strategy": "merge"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "fake-group",
"kind": "retainKeysMergeItem",
"version": "some-version"
}
]
},
"io.k8s.apimachinery.pkg.runtime.RawExtension": {
"description": "RawExtension is used to hold extensions in external versions.",
"required": [
"Raw"
],
"properties": {
"Raw": {
"description": "Raw is the underlying serialization of this object.",
"type": "string",
"format": "byte"
}
}
}
}
}

View File

@ -0,0 +1,47 @@
{
"swagger": "2.0",
"info": {
"title": "StrategicMergePatchTestingPrecisionItem",
"version": "v1.9.0"
},
"paths": {},
"definitions": {
"precisionItem": {
"description": "PrecisionItem is type definition for testing strategic merge.",
"required": [],
"properties": {
"name": {
"description": "Name field.",
"type": "string"
},
"int32": {
"description": "Int32 field.",
"type": "integer",
"format": "int32"
},
"int64": {
"description": "Int64 field.",
"type": "integer",
"format": "int64"
},
"float32": {
"description": "Float32 field.",
"type": "number",
"format": "float32"
},
"float64": {
"description": "Float64 field.",
"type": "number",
"format": "float64"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "fake-group",
"kind": "precisionItem",
"version": "some-version"
}
]
}
}
}

View File

@ -0,0 +1,30 @@
package(default_visibility = ["//visibility:public"])
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["openapi.go"],
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch/testing",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/googleapis/gnostic/compiler:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto: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

@ -0,0 +1,84 @@
/*
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 (
"io/ioutil"
"os"
"sync"
"gopkg.in/yaml.v2"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
openapi "k8s.io/kube-openapi/pkg/util/proto"
)
// Fake opens and returns a openapi swagger from a file Path. It will
// parse only once and then return the same copy everytime.
type Fake struct {
Path string
once sync.Once
document *openapi_v2.Document
err error
}
// OpenAPISchema returns the openapi document and a potential error.
func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) {
f.once.Do(func() {
_, err := os.Stat(f.Path)
if err != nil {
f.err = err
return
}
spec, err := ioutil.ReadFile(f.Path)
if err != nil {
f.err = err
return
}
var info yaml.MapSlice
err = yaml.Unmarshal(spec, &info)
if err != nil {
f.err = err
return
}
f.document, f.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
})
return f.document, f.err
}
func getSchema(f Fake, model string) (openapi.Schema, error) {
s, err := f.OpenAPISchema()
if err != nil {
return nil, err
}
m, err := openapi.NewOpenAPIData(s)
if err != nil {
return nil, err
}
return m.LookupModel(model), nil
}
// GetSchemaOrDie returns returns the openapi schema.
func GetSchemaOrDie(f Fake, model string) openapi.Schema {
s, err := getSchema(f, model)
if err != nil {
panic(err)
}
return s
}

View File

@ -0,0 +1,193 @@
/*
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 strategicpatch
import (
"errors"
"strings"
"k8s.io/apimachinery/pkg/util/mergepatch"
openapi "k8s.io/kube-openapi/pkg/util/proto"
)
const (
patchStrategyOpenapiextensionKey = "x-kubernetes-patch-strategy"
patchMergeKeyOpenapiextensionKey = "x-kubernetes-patch-merge-key"
)
type LookupPatchItem interface {
openapi.SchemaVisitor
Error() error
Path() *openapi.Path
}
type kindItem struct {
key string
path *openapi.Path
err error
patchmeta PatchMeta
subschema openapi.Schema
hasVisitKind bool
}
func NewKindItem(key string, path *openapi.Path) *kindItem {
return &kindItem{
key: key,
path: path,
}
}
var _ LookupPatchItem = &kindItem{}
func (item *kindItem) Error() error {
return item.err
}
func (item *kindItem) Path() *openapi.Path {
return item.path
}
func (item *kindItem) VisitPrimitive(schema *openapi.Primitive) {
item.err = errors.New("expected kind, but got primitive")
}
func (item *kindItem) VisitArray(schema *openapi.Array) {
item.err = errors.New("expected kind, but got slice")
}
func (item *kindItem) VisitMap(schema *openapi.Map) {
item.err = errors.New("expected kind, but got map")
}
func (item *kindItem) VisitReference(schema openapi.Reference) {
if !item.hasVisitKind {
schema.SubSchema().Accept(item)
}
}
func (item *kindItem) VisitKind(schema *openapi.Kind) {
subschema, ok := schema.Fields[item.key]
if !ok {
item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
return
}
mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
if err != nil {
item.err = err
return
}
item.patchmeta = PatchMeta{
patchStrategies: patchStrategies,
patchMergeKey: mergeKey,
}
item.subschema = subschema
}
type sliceItem struct {
key string
path *openapi.Path
err error
patchmeta PatchMeta
subschema openapi.Schema
hasVisitKind bool
}
func NewSliceItem(key string, path *openapi.Path) *sliceItem {
return &sliceItem{
key: key,
path: path,
}
}
var _ LookupPatchItem = &sliceItem{}
func (item *sliceItem) Error() error {
return item.err
}
func (item *sliceItem) Path() *openapi.Path {
return item.path
}
func (item *sliceItem) VisitPrimitive(schema *openapi.Primitive) {
item.err = errors.New("expected slice, but got primitive")
}
func (item *sliceItem) VisitArray(schema *openapi.Array) {
if !item.hasVisitKind {
item.err = errors.New("expected visit kind first, then visit array")
}
subschema := schema.SubType
item.subschema = subschema
}
func (item *sliceItem) VisitMap(schema *openapi.Map) {
item.err = errors.New("expected slice, but got map")
}
func (item *sliceItem) VisitReference(schema openapi.Reference) {
if !item.hasVisitKind {
schema.SubSchema().Accept(item)
} else {
item.subschema = schema.SubSchema()
}
}
func (item *sliceItem) VisitKind(schema *openapi.Kind) {
subschema, ok := schema.Fields[item.key]
if !ok {
item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
return
}
mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
if err != nil {
item.err = err
return
}
item.patchmeta = PatchMeta{
patchStrategies: patchStrategies,
patchMergeKey: mergeKey,
}
item.hasVisitKind = true
subschema.Accept(item)
}
func parsePatchMetadata(extensions map[string]interface{}) (string, []string, error) {
ps, foundPS := extensions[patchStrategyOpenapiextensionKey]
var patchStrategies []string
var mergeKey, patchStrategy string
var ok bool
if foundPS {
patchStrategy, ok = ps.(string)
if ok {
patchStrategies = strings.Split(patchStrategy, ",")
} else {
return "", nil, mergepatch.ErrBadArgType(patchStrategy, ps)
}
}
mk, foundMK := extensions[patchMergeKeyOpenapiextensionKey]
if foundMK {
mergeKey, ok = mk.(string)
if !ok {
return "", nil, mergepatch.ErrBadArgType(mergeKey, mk)
}
}
return mergeKey, patchStrategies, nil
}

29
vendor/k8s.io/apimachinery/pkg/util/uuid/BUILD generated vendored Normal file
View File

@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["uuid.go"],
importpath = "k8s.io/apimachinery/pkg/util/uuid",
deps = [
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

43
vendor/k8s.io/apimachinery/pkg/util/uuid/uuid.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
/*
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 uuid
import (
"sync"
"github.com/pborman/uuid"
"k8s.io/apimachinery/pkg/types"
)
var uuidLock sync.Mutex
var lastUUID uuid.UUID
func NewUUID() types.UID {
uuidLock.Lock()
defer uuidLock.Unlock()
result := uuid.NewUUID()
// The UUID package is naive and can generate identical UUIDs if the
// time interval is quick enough.
// The UUID uses 100 ns increments so it's short enough to actively
// wait for a new value.
for uuid.Equal(lastUUID, result) == true {
result = uuid.NewUUID()
}
lastUUID = result
return types.UID(result.String())
}

38
vendor/k8s.io/apimachinery/pkg/util/validation/BUILD generated vendored Normal file
View File

@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
importpath = "k8s.io/apimachinery/pkg/util/validation",
library = ":go_default_library",
deps = ["//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["validation.go"],
importpath = "k8s.io/apimachinery/pkg/util/validation",
deps = ["//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"errors_test.go",
"path_test.go",
],
importpath = "k8s.io/apimachinery/pkg/util/validation/field",
library = ":go_default_library",
)
go_library(
name = "go_default_library",
srcs = [
"errors.go",
"path.go",
],
importpath = "k8s.io/apimachinery/pkg/util/validation/field",
deps = [
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/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"],
)

View File

@ -0,0 +1,259 @@
/*
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 field
import (
"fmt"
"reflect"
"strconv"
"strings"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
)
// Error is an implementation of the 'error' interface, which represents a
// field-level validation error.
type Error struct {
Type ErrorType
Field string
BadValue interface{}
Detail string
}
var _ error = &Error{}
// Error implements the error interface.
func (v *Error) Error() string {
return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
}
// ErrorBody returns the error message without the field name. This is useful
// for building nice-looking higher-level error reporting.
func (v *Error) ErrorBody() string {
var s string
switch v.Type {
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
s = fmt.Sprintf("%s", v.Type)
default:
value := v.BadValue
valueType := reflect.TypeOf(value)
if value == nil || valueType == nil {
value = "null"
} else if valueType.Kind() == reflect.Ptr {
if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() {
value = "null"
} else {
value = reflectValue.Elem().Interface()
}
}
switch t := value.(type) {
case int64, int32, float64, float32, bool:
// use simple printer for simple types
s = fmt.Sprintf("%s: %v", v.Type, value)
case string:
s = fmt.Sprintf("%s: %q", v.Type, t)
case fmt.Stringer:
// anything that defines String() is better than raw struct
s = fmt.Sprintf("%s: %s", v.Type, t.String())
default:
// fallback to raw struct
// TODO: internal types have panic guards against json.Marshalling to prevent
// accidental use of internal types in external serialized form. For now, use
// %#v, although it would be better to show a more expressive output in the future
s = fmt.Sprintf("%s: %#v", v.Type, value)
}
}
if len(v.Detail) != 0 {
s += fmt.Sprintf(": %s", v.Detail)
}
return s
}
// ErrorType is a machine readable value providing more detail about why
// a field is invalid. These values are expected to match 1-1 with
// CauseType in api/types.go.
type ErrorType string
// TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it.
const (
// ErrorTypeNotFound is used to report failure to find a requested value
// (e.g. looking up an ID). See NotFound().
ErrorTypeNotFound ErrorType = "FieldValueNotFound"
// ErrorTypeRequired is used to report required values that are not
// provided (e.g. empty strings, null values, or empty arrays). See
// Required().
ErrorTypeRequired ErrorType = "FieldValueRequired"
// ErrorTypeDuplicate is used to report collisions of values that must be
// unique (e.g. unique IDs). See Duplicate().
ErrorTypeDuplicate ErrorType = "FieldValueDuplicate"
// ErrorTypeInvalid is used to report malformed values (e.g. failed regex
// match, too long, out of bounds). See Invalid().
ErrorTypeInvalid ErrorType = "FieldValueInvalid"
// ErrorTypeNotSupported is used to report unknown values for enumerated
// fields (e.g. a list of valid values). See NotSupported().
ErrorTypeNotSupported ErrorType = "FieldValueNotSupported"
// ErrorTypeForbidden is used to report valid (as per formatting rules)
// values which would be accepted under some conditions, but which are not
// permitted by the current conditions (such as security policy). See
// Forbidden().
ErrorTypeForbidden ErrorType = "FieldValueForbidden"
// ErrorTypeTooLong is used to report that the given value is too long.
// This is similar to ErrorTypeInvalid, but the error will not include the
// too-long value. See TooLong().
ErrorTypeTooLong ErrorType = "FieldValueTooLong"
// ErrorTypeInternal is used to report other errors that are not related
// to user input. See InternalError().
ErrorTypeInternal ErrorType = "InternalError"
)
// String converts a ErrorType into its corresponding canonical error message.
func (t ErrorType) String() string {
switch t {
case ErrorTypeNotFound:
return "Not found"
case ErrorTypeRequired:
return "Required value"
case ErrorTypeDuplicate:
return "Duplicate value"
case ErrorTypeInvalid:
return "Invalid value"
case ErrorTypeNotSupported:
return "Unsupported value"
case ErrorTypeForbidden:
return "Forbidden"
case ErrorTypeTooLong:
return "Too long"
case ErrorTypeInternal:
return "Internal error"
default:
panic(fmt.Sprintf("unrecognized validation error: %q", string(t)))
}
}
// NotFound returns a *Error indicating "value not found". This is
// used to report failure to find a requested value (e.g. looking up an ID).
func NotFound(field *Path, value interface{}) *Error {
return &Error{ErrorTypeNotFound, field.String(), value, ""}
}
// Required returns a *Error indicating "value required". This is used
// to report required values that are not provided (e.g. empty strings, null
// values, or empty arrays).
func Required(field *Path, detail string) *Error {
return &Error{ErrorTypeRequired, field.String(), "", detail}
}
// Duplicate returns a *Error indicating "duplicate value". This is
// used to report collisions of values that must be unique (e.g. names or IDs).
func Duplicate(field *Path, value interface{}) *Error {
return &Error{ErrorTypeDuplicate, field.String(), value, ""}
}
// Invalid returns a *Error indicating "invalid value". This is used
// to report malformed values (e.g. failed regex match, too long, out of bounds).
func Invalid(field *Path, value interface{}, detail string) *Error {
return &Error{ErrorTypeInvalid, field.String(), value, detail}
}
// NotSupported returns a *Error indicating "unsupported value".
// This is used to report unknown values for enumerated fields (e.g. a list of
// valid values).
func NotSupported(field *Path, value interface{}, validValues []string) *Error {
detail := ""
if validValues != nil && len(validValues) > 0 {
quotedValues := make([]string, len(validValues))
for i, v := range validValues {
quotedValues[i] = strconv.Quote(v)
}
detail = "supported values: " + strings.Join(quotedValues, ", ")
}
return &Error{ErrorTypeNotSupported, field.String(), value, detail}
}
// Forbidden returns a *Error indicating "forbidden". This is used to
// report valid (as per formatting rules) values which would be accepted under
// some conditions, but which are not permitted by current conditions (e.g.
// security policy).
func Forbidden(field *Path, detail string) *Error {
return &Error{ErrorTypeForbidden, field.String(), "", detail}
}
// TooLong returns a *Error indicating "too long". This is used to
// report that the given value is too long. This is similar to
// Invalid, but the returned error will not include the too-long
// value.
func TooLong(field *Path, value interface{}, maxLength int) *Error {
return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d characters", maxLength)}
}
// InternalError returns a *Error indicating "internal error". This is used
// to signal that an error was found that was not directly related to user
// input. The err argument must be non-nil.
func InternalError(field *Path, err error) *Error {
return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
}
// ErrorList holds a set of Errors. It is plausible that we might one day have
// non-field errors in this same umbrella package, but for now we don't, so
// we can keep it simple and leave ErrorList here.
type ErrorList []*Error
// NewErrorTypeMatcher returns an errors.Matcher that returns true
// if the provided error is a Error and has the provided ErrorType.
func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
return func(err error) bool {
if e, ok := err.(*Error); ok {
return e.Type == t
}
return false
}
}
// ToAggregate converts the ErrorList into an errors.Aggregate.
func (list ErrorList) ToAggregate() utilerrors.Aggregate {
errs := make([]error, 0, len(list))
errorMsgs := sets.NewString()
for _, err := range list {
msg := fmt.Sprintf("%v", err)
if errorMsgs.Has(msg) {
continue
}
errorMsgs.Insert(msg)
errs = append(errs, err)
}
return utilerrors.NewAggregate(errs)
}
func fromAggregate(agg utilerrors.Aggregate) ErrorList {
errs := agg.Errors()
list := make(ErrorList, len(errs))
for i := range errs {
list[i] = errs[i].(*Error)
}
return list
}
// Filter removes items from the ErrorList that match the provided fns.
func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
err := utilerrors.FilterOut(list.ToAggregate(), fns...)
if err == nil {
return nil
}
// FilterOut takes an Aggregate and returns an Aggregate
return fromAggregate(err.(utilerrors.Aggregate))
}

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