Switch to github.com/golang/dep for vendoring

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
Mrunal Patel 2017-01-31 16:45:59 -08:00
parent d6ab91be27
commit 8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions

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

@ -0,0 +1,280 @@
/*
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...)
} else {
if !reflect.DeepEqual(a.Field(i).Interface(), b.Field(i).Interface()) {
changes = append(changes, diff{path: path, a: a.Field(i).Interface(), b: b.Field(i).Interface()})
}
}
}
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
}
for i := 0; i < l; i++ {
if !reflect.DeepEqual(a.Index(i), b.Index(i)) {
return objectReflectDiff(path.Index(i), a.Index(i), b.Index(i))
}
}
var diffs []diff
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)})
}
if len(diffs) == 0 {
diffs = append(diffs, diff{path: path, a: a, b: b})
}
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()
}

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

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

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"

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

@ -0,0 +1,182 @@
/*
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"
)
// 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)
}
// 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,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 errors
import (
"fmt"
"reflect"
"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 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)
}
}
}
}

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

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,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,280 @@
/*
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"
"crypto/tls"
"encoding/base64"
"fmt"
"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"
"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)
}
// NewRoundTripper creates a new SpdyRoundTripper that will use
// the specified tlsConfig.
func NewRoundTripper(tlsConfig *tls.Config) httpstream.UpgradeRoundTripper {
return NewSpdyRoundTripper(tlsConfig)
}
// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use
// the specified tlsConfig. This function is mostly meant for unit tests.
func NewSpdyRoundTripper(tlsConfig *tls.Config) *SpdyRoundTripper {
return &SpdyRoundTripper{tlsConfig: tlsConfig}
}
// 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 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 = 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
}
if s.tlsConfig == nil {
s.tlsConfig = &tls.Config{}
}
if len(s.tlsConfig.ServerName) == 0 {
s.tlsConfig.ServerName = host
}
tlsConn := tls.Client(rwc, s.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 s.tlsConfig != nil && s.tlsConfig.InsecureSkipVerify {
return tlsConn, nil
}
if err := tlsConn.VerifyHostname(host); 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
}
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) {
// TODO what's the best way to clone the request?
r := *req
req = &r
req.Header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
req.Header.Add(httpstream.HeaderUpgrade, HeaderSpdy31)
conn, err := s.dial(req)
if err != nil {
return nil, err
}
err = req.Write(conn)
if err != nil {
return nil, err
}
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil {
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,424 @@
/*
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"
"io"
"net/http"
"net/http/httptest"
"net/url"
"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) {
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)
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)
}
}
}
// 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-----
MIIBcjCCAR6gAwIBAgIQBOUTYowZaENkZi0faI9DgTALBgkqhkiG9w0BAQswEjEQ
MA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2MDAw
MFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCZ
xfR3sgeHBraGFfF/24tTn4PRVAHOf2UOOxSQRs+aYjNqimFqf/SRIblQgeXdBJDR
gVK5F1Js2zwlehw0bHxRAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIApDATBgNVHSUE
DDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4YW1w
bGUuY29tMAsGCSqGSIb3DQEBCwNBAI/mfBB8dm33IpUl+acSyWfL6gX5Wc0FFyVj
dKeesE1XBuPX1My/rzU6Oy/YwX7LOL4FaeNUS6bbL4axSLPKYSs=
-----END CERTIFICATE-----`)
var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAJnF9HeyB4cGtoYV8X/bi1Ofg9FUAc5/ZQ47FJBGz5piM2qKYWp/
9JEhuVCB5d0EkNGBUrkXUmzbPCV6HDRsfFECAwEAAQJBAJLH9yPuButniACTn5L5
IJQw1mWQt6zBw9eCo41YWkA0866EgjC53aPZaRjXMp0uNJGdIsys2V5rCOOLWN2C
ODECIQDICHsi8QQQ9wpuJy8X5l8MAfxHL+DIqI84wQTeVM91FQIhAMTME8A18/7h
1Ad6drdnxAkuC0tX6Sx0LDozrmen+HFNAiAlcEDrt0RVkIcpOrg7tuhPLQf0oudl
Zvb3Xlj069awSQIgcT15E/43w2+RASifzVNhQ2MCTr1sSA8lL+xzK+REmnUCIBhQ
j4139pf8Re1J50zBxS/JlQfgDQi9sO9pYeiHIxNs
-----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-----
MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
-----END CERTIFICATE-----`)
// localhostKey is the private key for localhostCert.
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
-----END RSA PRIVATE KEY-----`)

View file

@ -0,0 +1,78 @@
/*
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 (
"fmt"
"net/http"
"strings"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/httpstream"
)
const HeaderSpdy31 = "SPDY/3.1"
// responseUpgrader knows how to upgrade HTTP responses. It
// implements the httpstream.ResponseUpgrader interface.
type responseUpgrader struct {
}
// 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, _, err := hijacker.Hijack()
if err != nil {
runtime.HandleError(fmt.Errorf("unable to upgrade: error hijacking response: %v", err))
return nil
}
spdyConn, err := NewServerConnection(conn, 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,374 @@
/*
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.
const _ = proto.GoGoProtoPackageIsVersion1
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")
)
var fileDescriptorGenerated = []byte{
// 288 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x31, 0x4b, 0xf4, 0x30,
0x1c, 0xc6, 0x93, 0xf7, 0xee, 0x3d, 0xb4, 0x82, 0x43, 0x71, 0x38, 0x1c, 0xd2, 0xa2, 0x20, 0x5d,
0x4c, 0x56, 0x71, 0xec, 0x76, 0x20, 0x08, 0x3d, 0x71, 0x70, 0x6b, 0xef, 0x62, 0x2e, 0xf4, 0x2e,
0x09, 0xe9, 0xbf, 0x42, 0xb7, 0xfb, 0x08, 0xba, 0x39, 0xfa, 0x71, 0x3a, 0xde, 0xe8, 0x20, 0x87,
0xad, 0xdf, 0xc2, 0x49, 0x9a, 0x16, 0x74, 0x4a, 0x9e, 0xe7, 0xf9, 0xfd, 0x02, 0xf1, 0x6e, 0xf2,
0xab, 0x82, 0x4a, 0xcd, 0xf2, 0x32, 0xe3, 0x56, 0x71, 0xe0, 0x05, 0x7b, 0xe2, 0x6a, 0xa9, 0x2d,
0x1b, 0x86, 0xd4, 0xc8, 0x4d, 0xba, 0x58, 0x49, 0xc5, 0x6d, 0xc5, 0x4c, 0x2e, 0x58, 0x09, 0x72,
0xcd, 0xa4, 0x82, 0x02, 0x2c, 0x13, 0x5c, 0x71, 0x9b, 0x02, 0x5f, 0x52, 0x63, 0x35, 0x68, 0xff,
0xbc, 0x97, 0xe8, 0x5f, 0x89, 0x9a, 0x5c, 0xd0, 0x4e, 0xa2, 0xbd, 0x74, 0x7a, 0x29, 0x24, 0xac,
0xca, 0x8c, 0x2e, 0xf4, 0x86, 0x09, 0x2d, 0x34, 0x73, 0x6e, 0x56, 0x3e, 0xba, 0xe4, 0x82, 0xbb,
0xf5, 0x6f, 0x9e, 0xbd, 0x60, 0xef, 0x68, 0xa6, 0xe0, 0xd6, 0xce, 0xc1, 0x4a, 0x25, 0xfc, 0xc8,
0x1b, 0x43, 0x65, 0xf8, 0x14, 0x87, 0x38, 0x1a, 0xc5, 0x27, 0xf5, 0x3e, 0x40, 0xed, 0x3e, 0x18,
0xdf, 0x55, 0x86, 0x7f, 0x0f, 0x67, 0xe2, 0x08, 0xff, 0xc2, 0x9b, 0x48, 0x05, 0xf7, 0xe9, 0x7a,
0xfa, 0x2f, 0xc4, 0xd1, 0xff, 0xf8, 0x78, 0x60, 0x27, 0x33, 0xd7, 0x26, 0xc3, 0xda, 0x71, 0x05,
0xd8, 0x8e, 0x1b, 0x85, 0x38, 0x3a, 0xfc, 0xe5, 0xe6, 0xae, 0x4d, 0x86, 0xf5, 0xfa, 0xe0, 0xf5,
0x2d, 0x40, 0xdb, 0x8f, 0x10, 0xc5, 0x51, 0xdd, 0x10, 0xb4, 0x6b, 0x08, 0x7a, 0x6f, 0x08, 0xda,
0xb6, 0x04, 0xd7, 0x2d, 0xc1, 0xbb, 0x96, 0xe0, 0xcf, 0x96, 0xe0, 0xe7, 0x2f, 0x82, 0x1e, 0x26,
0xfd, 0x67, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd6, 0x55, 0xdf, 0x2a, 0x60, 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"
"k8s.io/apimachinery/pkg/openapi"
"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)
}
}
}

107
vendor/k8s.io/apimachinery/pkg/util/json/json.go generated vendored Normal file
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 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)
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()
}

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

@ -0,0 +1,317 @@
/*
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: strconv.FormatFloat(9223372036854775808, 'g', -1, 64),
},
{
In: `-9223372036854775809`, // MinInt64 - 1
Data: float64(math.MinInt64),
Out: strconv.FormatFloat(-9223372036854775809, 'g', -1, 64),
},
// 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
}
}
}

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

@ -0,0 +1,269 @@
/*
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"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"github.com/golang/glog"
"golang.org/x/net/http2"
)
// 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 Dialer(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 Dialer(transport.WrappedRoundTripper())
default:
return nil, fmt.Errorf("unknown transport type: %v", transport)
}
}
// CloneTLSConfig returns a tls.Config with all exported fields except SessionTicketsDisabled and SessionTicketKey copied.
// This makes it safe to call CloneTLSConfig on a config in active use by a server.
// TODO: replace with tls.Config#Clone when we move to go1.8
func CloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}
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: %v", 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"
}
// 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 {
hdr := req.Header
// First check the X-Forwarded-For header for requests via proxy.
hdrForwardedFor := hdr.Get("X-Forwarded-For")
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 {
return ip
}
}
}
// Try the X-Real-Ip header.
hdrRealIp := hdr.Get("X-Real-Ip")
if hdrRealIp != "" {
ip := net.ParseIP(hdrRealIp)
if ip != nil {
return 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 {
return net.ParseIP(host)
}
// Fallback if Remote Address was just IP.
return net.ParseIP(req.RemoteAddr)
}
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 envvar has a CIDR in it
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) {
host := req.URL.Host
// for some urls, the Host is already the host, not the host:port
if net.ParseIP(host) == nil {
var err error
host, _, err = net.SplitHostPort(req.URL.Host)
if err != nil {
return delegate(req)
}
}
ip := net.ParseIP(host)
if ip == nil {
return delegate(req)
}
for _, cidr := range cidrs {
if cidr.Contains(ip) {
return nil, nil
}
}
return delegate(req)
}
}

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

@ -0,0 +1,246 @@
/*
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"
"net"
"net/http"
"net/url"
"os"
"reflect"
"runtime"
"strings"
"testing"
"k8s.io/apimachinery/pkg/util/sets"
)
func TestCloneTLSConfig(t *testing.T) {
expected := sets.NewString(
// These fields are copied in CloneTLSConfig
"Rand",
"Time",
"Certificates",
"RootCAs",
"NextProtos",
"ServerName",
"InsecureSkipVerify",
"CipherSuites",
"PreferServerCipherSuites",
"MinVersion",
"MaxVersion",
"CurvePreferences",
"NameToCertificate",
"GetCertificate",
"ClientAuth",
"ClientCAs",
"ClientSessionCache",
// These fields are not copied
"SessionTicketsDisabled",
"SessionTicketKey",
// These fields are unexported
"serverInitOnce",
"mutex",
"sessionTicketKeys",
)
// See #33936.
if strings.HasPrefix(runtime.Version(), "go1.7") {
expected.Insert("DynamicRecordSizingDisabled", "Renegotiation")
}
fields := sets.NewString()
structType := reflect.TypeOf(tls.Config{})
for i := 0; i < structType.NumField(); i++ {
fields.Insert(structType.Field(i).Name)
}
if missing := expected.Difference(fields); len(missing) > 0 {
t.Errorf("Expected fields that were not seen in http.Transport: %v", missing.List())
}
if extra := fields.Difference(expected); len(extra) > 0 {
t.Errorf("New fields seen in http.Transport: %v\nAdd to CopyClientTLSConfig if client-relevant, then add to expected list in TestCopyClientTLSConfig", extra.List())
}
}
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 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,
},
}
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")
}
}

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

@ -0,0 +1,278 @@
/*
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 Route struct {
Interface string
Destination net.IP
Gateway net.IP
// TODO: add more fields here if needed
}
func getRoutes(input io.Reader) ([]Route, error) {
routes := []Route{}
if input == nil {
return nil, fmt.Errorf("input is nil")
}
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)
routes = append(routes, Route{})
route := &routes[len(routes)-1]
route.Interface = fields[0]
ip, err := parseIP(fields[1])
if err != nil {
return nil, err
}
route.Destination = ip
ip, err = parseIP(fields[2])
if err != nil {
return nil, err
}
route.Gateway = ip
}
return routes, nil
}
func parseIP(str string) (net.IP, error) {
if str == "" {
return nil, fmt.Errorf("input is nil")
}
bytes, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
//TODO add ipv6 support
if len(bytes) != net.IPv4len {
return nil, fmt.Errorf("only IPv4 is supported")
}
bytes[0], bytes[1], bytes[2], bytes[3] = bytes[3], bytes[2], bytes[1], bytes[0]
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
}
//getFinalIP method receives all the IP addrs of a Interface
//and returns a nil if the address is Loopback, Ipv6, link-local or nil.
//It returns a valid IPv4 if an Ipv4 address is found in the array.
func getFinalIP(addrs []net.Addr) (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
}
//Only IPv4
//TODO : add IPv6 support
if ip.To4() != nil {
if !ip.IsLoopback() && !ip.IsLinkLocalMulticast() && !ip.IsLinkLocalUnicast() {
glog.V(4).Infof("IP found %v", ip)
return ip, nil
} else {
glog.V(4).Infof("Loopback/link-local found %v", ip)
}
} else {
glog.V(4).Infof("%v is not a valid IPv4 address", ip)
}
}
}
return nil, nil
}
func getIPFromInterface(intfName string, 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)
finalIP, err := getFinalIP(addrs)
if err != nil {
return nil, err
}
if finalIP != nil {
glog.V(4).Infof("valid IPv4 address for interface %q found as %v.", intfName, finalIP)
return finalIP, nil
}
}
return nil, nil
}
func flagsSet(flags net.Flags, test net.Flags) bool {
return flags&test != 0
}
func flagsClear(flags net.Flags, test net.Flags) bool {
return flags&test == 0
}
func chooseHostInterfaceNativeGo() (net.IP, error) {
intfs, err := net.Interfaces()
if err != nil {
return nil, err
}
i := 0
var ip net.IP
for i = range intfs {
if flagsSet(intfs[i].Flags, net.FlagUp) && flagsClear(intfs[i].Flags, net.FlagLoopback|net.FlagPointToPoint) {
addrs, err := intfs[i].Addrs()
if err != nil {
return nil, err
}
if len(addrs) > 0 {
for _, addr := range addrs {
if addrIP, _, err := net.ParseCIDR(addr.String()); err == nil {
if addrIP.To4() != nil {
ip = addrIP.To4()
if !ip.IsLinkLocalMulticast() && !ip.IsLinkLocalUnicast() {
break
}
}
}
}
if ip != nil {
// This interface should suffice.
break
}
}
}
}
if ip == nil {
return nil, fmt.Errorf("no acceptable interface from host")
}
glog.V(4).Infof("Choosing interface %s (IP %v) as default", intfs[i].Name, ip)
return ip, nil
}
//ChooseHostInterface is a method used fetch an IP for a daemon.
//It uses data from /proc/net/route file.
//For a node with no internet connection ,it returns error
//For a multi n/w interface node it returns the IP of the interface with gateway on it.
func ChooseHostInterface() (net.IP, error) {
inFile, err := os.Open("/proc/net/route")
if err != nil {
if os.IsNotExist(err) {
return chooseHostInterfaceNativeGo()
}
return nil, err
}
defer inFile.Close()
var nw networkInterfacer = networkInterface{}
return chooseHostInterfaceFromRoute(inFile, nw)
}
type networkInterfacer interface {
InterfaceByName(intfName string) (*net.Interface, error)
Addrs(intf *net.Interface) ([]net.Addr, error)
}
type networkInterface struct{}
func (_ networkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
intf, err := net.InterfaceByName(intfName)
if err != nil {
return nil, err
}
return intf, nil
}
func (_ networkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
addrs, err := intf.Addrs()
if err != nil {
return nil, err
}
return addrs, nil
}
func chooseHostInterfaceFromRoute(inFile io.Reader, nw networkInterfacer) (net.IP, error) {
routes, err := getRoutes(inFile)
if err != nil {
return nil, err
}
zero := net.IP{0, 0, 0, 0}
var finalIP net.IP
for i := range routes {
//find interface with gateway
if routes[i].Destination.Equal(zero) {
glog.V(4).Infof("Default route transits interface %q", routes[i].Interface)
finalIP, err := getIPFromInterface(routes[i].Interface, nw)
if err != nil {
return nil, err
}
if finalIP != nil {
glog.V(4).Infof("Choosing IP %v ", finalIP)
return finalIP, nil
}
}
}
glog.V(4).Infof("No valid IP found")
if finalIP == nil {
return nil, fmt.Errorf("Unable to select an IP.")
}
return nil, nil
}
// 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,300 @@
/*
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"
"net"
"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 gatewayfirstIpv6_1 = `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 gatewayfirstIpv6_2 = `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
`
// Based on DigitalOcean COREOS
const gatewayfirstLinkLocal = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 0120372D 0001 0 0 0 00000000 0 0 0
eth0 00000000 00000000 0001 0 0 2048 00000000 0 0 0
`
func TestGetRoutes(t *testing.T) {
testCases := []struct {
tcase string
route string
expected int
}{
{"gatewayfirst", gatewayfirst, 4},
{"gatewaymiddle", gatewaymiddle, 4},
{"gatewaylast", gatewaylast, 4},
{"nothing", nothing, 0},
{"gatewayfirstIpv6_1", gatewayfirstIpv6_1, 0},
{"gatewayfirstIpv6_2", gatewayfirstIpv6_2, 0},
{"route_Invalidhex", route_Invalidhex, 0},
}
for _, tc := range testCases {
r := strings.NewReader(tc.route)
routes, err := getRoutes(r)
if len(routes) != tc.expected {
t.Errorf("case[%v]: expected %v, got %v .err : %v", tc.tcase, tc.expected, len(routes), err)
}
}
}
func TestParseIP(t *testing.T) {
testCases := []struct {
tcase string
ip string
success bool
expected net.IP
}{
{"empty", "", false, nil},
{"too short", "AA", false, nil},
{"too long", "0011223344", false, nil},
{"invalid", "invalid!", false, nil},
{"zero", "00000000", true, net.IP{0, 0, 0, 0}},
{"ffff", "FFFFFFFF", true, net.IP{0xff, 0xff, 0xff, 0xff}},
{"valid", "12345678", true, net.IP{120, 86, 52, 18}},
}
for _, tc := range testCases {
ip, err := parseIP(tc.ip)
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},
{"nothing", net.Interface{}, 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
expected net.IP
}{
{"ipv6", []net.Addr{addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}}, nil},
{"invalidCIDR", []net.Addr{addrStruct{val: "fe80::2f7:67fff:fe6e:2956/64"}}, nil},
{"loopback", []net.Addr{addrStruct{val: "127.0.0.1/24"}}, nil},
{"ip4", []net.Addr{addrStruct{val: "10.254.12.132/17"}}, net.ParseIP("10.254.12.132")},
{"nothing", []net.Addr{}, nil},
}
for _, tc := range testCases {
ip, err := getFinalIP(tc.addr)
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")
}
}
type validNetworkInterface struct {
}
func (_ validNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
c := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}
return &c, 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
}
type validNetworkInterfaceWithLinkLocal struct {
}
func (_ validNetworkInterfaceWithLinkLocal) InterfaceByName(intfName string) (*net.Interface, error) {
c := net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: net.FlagUp}
return &c, nil
}
func (_ validNetworkInterfaceWithLinkLocal) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "45.55.47.146/19"}}
return ifat, nil
}
type validNetworkInterfacewithIpv6Only struct {
}
func (_ validNetworkInterfacewithIpv6Only) InterfaceByName(intfName string) (*net.Interface, error) {
c := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}
return &c, nil
}
func (_ validNetworkInterfacewithIpv6Only) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}}
return ifat, nil
}
type noNetworkInterface struct {
}
func (_ noNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return nil, fmt.Errorf("unable get Interface")
}
func (_ noNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
return nil, nil
}
type networkInterfacewithNoAddrs struct {
}
func (_ networkInterfacewithNoAddrs) InterfaceByName(intfName string) (*net.Interface, error) {
c := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}
return &c, nil
}
func (_ networkInterfacewithNoAddrs) Addrs(intf *net.Interface) ([]net.Addr, error) {
return nil, fmt.Errorf("unable get Addrs")
}
type networkInterfacewithIpv6addrs struct {
}
func (_ networkInterfacewithIpv6addrs) InterfaceByName(intfName string) (*net.Interface, error) {
c := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}
return &c, nil
}
func (_ networkInterfacewithIpv6addrs) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "fe80::2f7:6ffff:fe6e:2956/64"}}
return ifat, nil
}
func TestGetIPFromInterface(t *testing.T) {
testCases := []struct {
tcase string
nwname string
nw networkInterfacer
expected net.IP
}{
{"valid", "eth3", validNetworkInterface{}, net.ParseIP("10.254.71.145")},
{"ipv6", "eth3", validNetworkInterfacewithIpv6Only{}, nil},
{"nothing", "eth3", noNetworkInterface{}, nil},
}
for _, tc := range testCases {
ip, err := getIPFromInterface(tc.nwname, tc.nw)
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
inFile io.Reader
nw networkInterfacer
expected net.IP
}{
{"valid_routefirst", strings.NewReader(gatewayfirst), validNetworkInterface{}, net.ParseIP("10.254.71.145")},
{"valid_routelast", strings.NewReader(gatewaylast), validNetworkInterface{}, net.ParseIP("10.254.71.145")},
{"valid_routemiddle", strings.NewReader(gatewaymiddle), validNetworkInterface{}, net.ParseIP("10.254.71.145")},
{"valid_routemiddle_ipv6", strings.NewReader(gatewaymiddle), validNetworkInterfacewithIpv6Only{}, nil},
{"no internet connection", strings.NewReader(noInternetConnection), validNetworkInterface{}, nil},
{"no non-link-local ip", strings.NewReader(gatewayfirstLinkLocal), validNetworkInterfaceWithLinkLocal{}, net.ParseIP("45.55.47.146")},
{"no route", strings.NewReader(nothing), validNetworkInterface{}, nil},
{"no route file", nil, validNetworkInterface{}, nil},
{"no interfaces", nil, noNetworkInterface{}, nil},
{"no interface Addrs", strings.NewReader(gatewaymiddle), networkInterfacewithNoAddrs{}, nil},
{"Invalid Addrs", strings.NewReader(gatewaymiddle), networkInterfacewithIpv6addrs{}, nil},
}
for _, tc := range testCases {
ip, err := chooseHostInterfaceFromRoute(tc.inFile, tc.nw)
if !ip.Equal(tc.expected) {
t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err)
}
}
}

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

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

@ -0,0 +1,85 @@
/*
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)
}
// We omit vowels from the set of available characters to reduce the chances
// of "bad words" being formed.
var alphanums = []rune("bcdfghjklmnpqrstvwxz0123456789")
// String generates a random alphanumeric string, without vowels, which is n
// characters long. This will panic if n is less than zero.
func String(length int) string {
b := make([]rune, length)
for i := range b {
b[i] = alphanums[Intn(len(alphanums))]
}
return string(b)
}

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

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

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

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

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
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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())
}

View file

@ -0,0 +1,235 @@
/*
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 (
"encoding/json"
"fmt"
"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:
var bad string
badBytes, err := json.Marshal(v.BadValue)
if err != nil {
bad = err.Error()
} else {
bad = string(badBytes)
}
s = fmt.Sprintf("%s: %s", v.Type, bad)
}
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 {
detail = "supported values: " + strings.Join(validValues, ", ")
}
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))
}

View file

@ -0,0 +1,158 @@
/*
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"
"strings"
"testing"
)
func TestMakeFuncs(t *testing.T) {
testCases := []struct {
fn func() *Error
expected ErrorType
}{
{
func() *Error { return Invalid(NewPath("f"), "v", "d") },
ErrorTypeInvalid,
},
{
func() *Error { return NotSupported(NewPath("f"), "v", nil) },
ErrorTypeNotSupported,
},
{
func() *Error { return Duplicate(NewPath("f"), "v") },
ErrorTypeDuplicate,
},
{
func() *Error { return NotFound(NewPath("f"), "v") },
ErrorTypeNotFound,
},
{
func() *Error { return Required(NewPath("f"), "d") },
ErrorTypeRequired,
},
{
func() *Error { return InternalError(NewPath("f"), fmt.Errorf("e")) },
ErrorTypeInternal,
},
}
for _, testCase := range testCases {
err := testCase.fn()
if err.Type != testCase.expected {
t.Errorf("expected Type %q, got %q", testCase.expected, err.Type)
}
}
}
func TestErrorUsefulMessage(t *testing.T) {
s := Invalid(NewPath("foo"), "bar", "deet").Error()
t.Logf("message: %v", s)
for _, part := range []string{"foo", "bar", "deet", ErrorTypeInvalid.String()} {
if !strings.Contains(s, part) {
t.Errorf("error message did not contain expected part '%v'", part)
}
}
type complicated struct {
Baz int
Qux string
Inner interface{}
KV map[string]int
}
s = Invalid(
NewPath("foo"),
&complicated{
Baz: 1,
Qux: "aoeu",
Inner: &complicated{Qux: "asdf"},
KV: map[string]int{"Billy": 2},
},
"detail",
).Error()
t.Logf("message: %v", s)
for _, part := range []string{
"foo", ErrorTypeInvalid.String(),
"Baz", "Qux", "Inner", "KV", "detail",
"1", "aoeu", "asdf", "Billy", "2",
} {
if !strings.Contains(s, part) {
t.Errorf("error message did not contain expected part '%v'", part)
}
}
}
func TestToAggregate(t *testing.T) {
testCases := struct {
ErrList []ErrorList
NumExpectedErrs []int
}{
[]ErrorList{
nil,
{},
{Invalid(NewPath("f"), "v", "d")},
{Invalid(NewPath("f"), "v", "d"), Invalid(NewPath("f"), "v", "d")},
{Invalid(NewPath("f"), "v", "d"), InternalError(NewPath(""), fmt.Errorf("e"))},
},
[]int{
0,
0,
1,
1,
2,
},
}
if len(testCases.ErrList) != len(testCases.NumExpectedErrs) {
t.Errorf("Mismatch: length of NumExpectedErrs does not match length of ErrList")
}
for i, tc := range testCases.ErrList {
agg := tc.ToAggregate()
numErrs := 0
if agg != nil {
numErrs = len(agg.Errors())
}
if numErrs != testCases.NumExpectedErrs[i] {
t.Errorf("[%d] Expected %d, got %d", i, testCases.NumExpectedErrs[i], numErrs)
}
if len(tc) == 0 {
if agg != nil {
t.Errorf("[%d] Expected nil, got %#v", i, agg)
}
} else if agg == nil {
t.Errorf("[%d] Expected non-nil", i)
}
}
}
func TestErrListFilter(t *testing.T) {
list := ErrorList{
Invalid(NewPath("test.field"), "", ""),
Invalid(NewPath("field.test"), "", ""),
Duplicate(NewPath("test"), "value"),
}
if len(list.Filter(NewErrorTypeMatcher(ErrorTypeDuplicate))) != 2 {
t.Errorf("should not filter")
}
if len(list.Filter(NewErrorTypeMatcher(ErrorTypeInvalid))) != 1 {
t.Errorf("should filter")
}
}

View file

@ -0,0 +1,91 @@
/*
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 field
import (
"bytes"
"fmt"
"strconv"
)
// Path represents the path from some root to a particular field.
type Path struct {
name string // the name of this field or "" if this is an index
index string // if name == "", this is a subscript (index or map key) of the previous element
parent *Path // nil if this is the root element
}
// NewPath creates a root Path object.
func NewPath(name string, moreNames ...string) *Path {
r := &Path{name: name, parent: nil}
for _, anotherName := range moreNames {
r = &Path{name: anotherName, parent: r}
}
return r
}
// Root returns the root element of this Path.
func (p *Path) Root() *Path {
for ; p.parent != nil; p = p.parent {
// Do nothing.
}
return p
}
// Child creates a new Path that is a child of the method receiver.
func (p *Path) Child(name string, moreNames ...string) *Path {
r := NewPath(name, moreNames...)
r.Root().parent = p
return r
}
// Index indicates that the previous Path is to be subscripted by an int.
// This sets the same underlying value as Key.
func (p *Path) Index(index int) *Path {
return &Path{index: strconv.Itoa(index), parent: p}
}
// Key indicates that the previous Path is to be subscripted by a string.
// This sets the same underlying value as Index.
func (p *Path) Key(key string) *Path {
return &Path{index: key, parent: p}
}
// String produces a string representation of the Path.
func (p *Path) String() string {
// make a slice to iterate
elems := []*Path{}
for ; p != nil; p = p.parent {
elems = append(elems, p)
}
// iterate, but it has to be backwards
buf := bytes.NewBuffer(nil)
for i := range elems {
p := elems[len(elems)-1-i]
if p.parent != nil && len(p.name) > 0 {
// This is either the root or it is a subscript.
buf.WriteString(".")
}
if len(p.name) > 0 {
buf.WriteString(p.name)
} else {
fmt.Fprintf(buf, "[%s]", p.index)
}
}
return buf.String()
}

View file

@ -0,0 +1,123 @@
/*
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 field
import "testing"
func TestPath(t *testing.T) {
testCases := []struct {
op func(*Path) *Path
expected string
}{
{
func(p *Path) *Path { return p },
"root",
},
{
func(p *Path) *Path { return p.Child("first") },
"root.first",
},
{
func(p *Path) *Path { return p.Child("second") },
"root.first.second",
},
{
func(p *Path) *Path { return p.Index(0) },
"root.first.second[0]",
},
{
func(p *Path) *Path { return p.Child("third") },
"root.first.second[0].third",
},
{
func(p *Path) *Path { return p.Index(93) },
"root.first.second[0].third[93]",
},
{
func(p *Path) *Path { return p.parent },
"root.first.second[0].third",
},
{
func(p *Path) *Path { return p.parent },
"root.first.second[0]",
},
{
func(p *Path) *Path { return p.Key("key") },
"root.first.second[0][key]",
},
}
root := NewPath("root")
p := root
for i, tc := range testCases {
p = tc.op(p)
if p.String() != tc.expected {
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String())
}
if p.Root() != root {
t.Errorf("[%d] Wrong root: %#v", i, p.Root())
}
}
}
func TestPathMultiArg(t *testing.T) {
testCases := []struct {
op func(*Path) *Path
expected string
}{
{
func(p *Path) *Path { return p },
"root.first",
},
{
func(p *Path) *Path { return p.Child("second", "third") },
"root.first.second.third",
},
{
func(p *Path) *Path { return p.Index(0) },
"root.first.second.third[0]",
},
{
func(p *Path) *Path { return p.parent },
"root.first.second.third",
},
{
func(p *Path) *Path { return p.parent },
"root.first.second",
},
{
func(p *Path) *Path { return p.parent },
"root.first",
},
{
func(p *Path) *Path { return p.parent },
"root",
},
}
root := NewPath("root", "first")
p := root
for i, tc := range testCases {
p = tc.op(p)
if p.String() != tc.expected {
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String())
}
if p.Root() != root.Root() {
t.Errorf("[%d] Wrong root: %#v", i, p.Root())
}
}
}

View file

@ -0,0 +1,343 @@
/*
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 validation
import (
"fmt"
"math"
"net"
"regexp"
"strings"
)
const qnameCharFmt string = "[A-Za-z0-9]"
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
const qualifiedNameMaxLength int = 63
var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
// IsQualifiedName tests whether the value passed is what Kubernetes calls a
// "qualified name". This is a format used in various places throughout the
// system. If the value is not valid, a list of error strings is returned.
// Otherwise an empty list (or nil) is returned.
func IsQualifiedName(value string) []string {
var errs []string
parts := strings.Split(value, "/")
var name string
switch len(parts) {
case 1:
name = parts[0]
case 2:
var prefix string
prefix, name = parts[0], parts[1]
if len(prefix) == 0 {
errs = append(errs, "prefix part "+EmptyError())
} else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
errs = append(errs, prefixEach(msgs, "prefix part ")...)
}
default:
return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
}
if len(name) == 0 {
errs = append(errs, "name part "+EmptyError())
} else if len(name) > qualifiedNameMaxLength {
errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
}
if !qualifiedNameRegexp.MatchString(name) {
errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
}
return errs
}
const labelValueFmt string = "(" + qualifiedNameFmt + ")?"
const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
const LabelValueMaxLength int = 63
var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
// IsValidLabelValue tests whether the value passed is a valid label value. If
// the value is not valid, a list of error strings is returned. Otherwise an
// empty list (or nil) is returned.
func IsValidLabelValue(value string) []string {
var errs []string
if len(value) > LabelValueMaxLength {
errs = append(errs, MaxLenError(LabelValueMaxLength))
}
if !labelValueRegexp.MatchString(value) {
errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
}
return errs
}
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
const dns1123LabelErrMsg string = "a DNS-1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
const DNS1123LabelMaxLength int = 63
var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
// IsDNS1123Label tests for a string that conforms to the definition of a label in
// DNS (RFC 1123).
func IsDNS1123Label(value string) []string {
var errs []string
if len(value) > DNS1123LabelMaxLength {
errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
}
if !dns1123LabelRegexp.MatchString(value) {
errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
}
return errs
}
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
const dns1123SubdomainErrorMsg string = "a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
const DNS1123SubdomainMaxLength int = 253
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
// subdomain in DNS (RFC 1123).
func IsDNS1123Subdomain(value string) []string {
var errs []string
if len(value) > DNS1123SubdomainMaxLength {
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
}
if !dns1123SubdomainRegexp.MatchString(value) {
errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
}
return errs
}
const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
const DNS1035LabelMaxLength int = 63
var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
// IsDNS1035Label tests for a string that conforms to the definition of a label in
// DNS (RFC 1035).
func IsDNS1035Label(value string) []string {
var errs []string
if len(value) > DNS1035LabelMaxLength {
errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
}
if !dns1035LabelRegexp.MatchString(value) {
errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
}
return errs
}
// wildcard definition - RFC 1034 section 4.3.3.
// examples:
// - valid: *.bar.com, *.foo.bar.com
// - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, *
const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt
const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character"
// IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a
// wildcard subdomain in DNS (RFC 1034 section 4.3.3).
func IsWildcardDNS1123Subdomain(value string) []string {
wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$")
var errs []string
if len(value) > DNS1123SubdomainMaxLength {
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
}
if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
}
return errs
}
const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*"
const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'"
var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$")
// IsCIdentifier tests for a string that conforms the definition of an identifier
// in C. This checks the format, but not the length.
func IsCIdentifier(value string) []string {
if !cIdentifierRegexp.MatchString(value) {
return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")}
}
return nil
}
// IsValidPortNum tests that the argument is a valid, non-zero port number.
func IsValidPortNum(port int) []string {
if 1 <= port && port <= 65535 {
return nil
}
return []string{InclusiveRangeError(1, 65535)}
}
// Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1
// TODO: once we have a type for UID/GID we should make these that type.
const (
minUserID = 0
maxUserID = math.MaxInt32
minGroupID = 0
maxGroupID = math.MaxInt32
)
// IsValidGroupId tests that the argument is a valid Unix GID.
func IsValidGroupId(gid int64) []string {
if minGroupID <= gid && gid <= maxGroupID {
return nil
}
return []string{InclusiveRangeError(minGroupID, maxGroupID)}
}
// IsValidUserId tests that the argument is a valid Unix UID.
func IsValidUserId(uid int64) []string {
if minUserID <= uid && uid <= maxUserID {
return nil
}
return []string{InclusiveRangeError(minUserID, maxUserID)}
}
var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$")
var portNameOneLetterRegexp = regexp.MustCompile("[a-z]")
// IsValidPortName check that the argument is valid syntax. It must be
// non-empty and no more than 15 characters long. It may contain only [-a-z0-9]
// and must contain at least one letter [a-z]. It must not start or end with a
// hyphen, nor contain adjacent hyphens.
//
// Note: We only allow lower-case characters, even though RFC 6335 is case
// insensitive.
func IsValidPortName(port string) []string {
var errs []string
if len(port) > 15 {
errs = append(errs, MaxLenError(15))
}
if !portNameCharsetRegex.MatchString(port) {
errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)")
}
if !portNameOneLetterRegexp.MatchString(port) {
errs = append(errs, "must contain at least one letter or number (a-z, 0-9)")
}
if strings.Contains(port, "--") {
errs = append(errs, "must not contain consecutive hyphens")
}
if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') {
errs = append(errs, "must not begin or end with a hyphen")
}
return errs
}
// IsValidIP tests that the argument is a valid IP address.
func IsValidIP(value string) []string {
if net.ParseIP(value) == nil {
return []string{"must be a valid IP address, (e.g. 10.9.8.7)"}
}
return nil
}
const percentFmt string = "[0-9]+%"
const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'"
var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
func IsValidPercent(percent string) []string {
if !percentRegexp.MatchString(percent) {
return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")}
}
return nil
}
const httpHeaderNameFmt string = "[-A-Za-z0-9]+"
const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'"
var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$")
// IsHTTPHeaderName checks that a string conforms to the Go HTTP library's
// definition of a valid header field name (a stricter subset than RFC7230).
func IsHTTPHeaderName(value string) []string {
if !httpHeaderNameRegexp.MatchString(value) {
return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")}
}
return nil
}
const configMapKeyFmt = `[-._a-zA-Z0-9]+`
const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'"
var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$")
// IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret
func IsConfigMapKey(value string) []string {
var errs []string
if len(value) > DNS1123SubdomainMaxLength {
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
}
if !configMapKeyRegexp.MatchString(value) {
errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name"))
}
if value == "." {
errs = append(errs, `must not be '.'`)
} else if value == ".." {
errs = append(errs, `must not be '..'`)
} else if strings.HasPrefix(value, "..") {
errs = append(errs, `must not start with '..'`)
}
return errs
}
// MaxLenError returns a string explanation of a "string too long" validation
// failure.
func MaxLenError(length int) string {
return fmt.Sprintf("must be no more than %d characters", length)
}
// RegexError returns a string explanation of a regex validation failure.
func RegexError(msg string, fmt string, examples ...string) string {
if len(examples) == 0 {
return msg + " (regex used for validation is '" + fmt + "')"
}
msg += " (e.g. "
for i := range examples {
if i > 0 {
msg += " or "
}
msg += "'" + examples[i] + "', "
}
msg += "regex used for validation is '" + fmt + "')"
return msg
}
// EmptyError returns a string explanation of a "must not be empty" validation
// failure.
func EmptyError() string {
return "must be non-empty"
}
func prefixEach(msgs []string, prefix string) []string {
for i := range msgs {
msgs[i] = prefix + msgs[i]
}
return msgs
}
// InclusiveRangeError returns a string explanation of a numeric "must be
// between" validation failure.
func InclusiveRangeError(lo, hi int) string {
return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi)
}

View file

@ -0,0 +1,436 @@
/*
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 validation
import (
"strings"
"testing"
)
func TestIsDNS1123Label(t *testing.T) {
goodValues := []string{
"a", "ab", "abc", "a1", "a-1", "a--1--2--b",
"0", "01", "012", "1a", "1-a", "1--a--b--2",
strings.Repeat("a", 63),
}
for _, val := range goodValues {
if msgs := IsDNS1123Label(val); len(msgs) != 0 {
t.Errorf("expected true for '%s': %v", val, msgs)
}
}
badValues := []string{
"", "A", "ABC", "aBc", "A1", "A-1", "1-A",
"-", "a-", "-a", "1-", "-1",
"_", "a_", "_a", "a_b", "1_", "_1", "1_2",
".", "a.", ".a", "a.b", "1.", ".1", "1.2",
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
strings.Repeat("a", 64),
}
for _, val := range badValues {
if msgs := IsDNS1123Label(val); len(msgs) == 0 {
t.Errorf("expected false for '%s'", val)
}
}
}
func TestIsDNS1123Subdomain(t *testing.T) {
goodValues := []string{
"a", "ab", "abc", "a1", "a-1", "a--1--2--b",
"0", "01", "012", "1a", "1-a", "1--a--b--2",
"a.a", "ab.a", "abc.a", "a1.a", "a-1.a", "a--1--2--b.a",
"a.1", "ab.1", "abc.1", "a1.1", "a-1.1", "a--1--2--b.1",
"0.a", "01.a", "012.a", "1a.a", "1-a.a", "1--a--b--2",
"0.1", "01.1", "012.1", "1a.1", "1-a.1", "1--a--b--2.1",
"a.b.c.d.e", "aa.bb.cc.dd.ee", "1.2.3.4.5", "11.22.33.44.55",
strings.Repeat("a", 253),
}
for _, val := range goodValues {
if msgs := IsDNS1123Subdomain(val); len(msgs) != 0 {
t.Errorf("expected true for '%s': %v", val, msgs)
}
}
badValues := []string{
"", "A", "ABC", "aBc", "A1", "A-1", "1-A",
"-", "a-", "-a", "1-", "-1",
"_", "a_", "_a", "a_b", "1_", "_1", "1_2",
".", "a.", ".a", "a..b", "1.", ".1", "1..2",
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
"A.a", "aB.a", "ab.A", "A1.a", "a1.A",
"A.1", "aB.1", "A1.1", "1A.1",
"0.A", "01.A", "012.A", "1A.a", "1a.A",
"A.B.C.D.E", "AA.BB.CC.DD.EE", "a.B.c.d.e", "aa.bB.cc.dd.ee",
"a@b", "a,b", "a_b", "a;b",
"a:b", "a%b", "a?b", "a$b",
strings.Repeat("a", 254),
}
for _, val := range badValues {
if msgs := IsDNS1123Subdomain(val); len(msgs) == 0 {
t.Errorf("expected false for '%s'", val)
}
}
}
func TestIsDNS1035Label(t *testing.T) {
goodValues := []string{
"a", "ab", "abc", "a1", "a-1", "a--1--2--b",
strings.Repeat("a", 63),
}
for _, val := range goodValues {
if msgs := IsDNS1035Label(val); len(msgs) != 0 {
t.Errorf("expected true for '%s': %v", val, msgs)
}
}
badValues := []string{
"0", "01", "012", "1a", "1-a", "1--a--b--2",
"", "A", "ABC", "aBc", "A1", "A-1", "1-A",
"-", "a-", "-a", "1-", "-1",
"_", "a_", "_a", "a_b", "1_", "_1", "1_2",
".", "a.", ".a", "a.b", "1.", ".1", "1.2",
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
strings.Repeat("a", 64),
}
for _, val := range badValues {
if msgs := IsDNS1035Label(val); len(msgs) == 0 {
t.Errorf("expected false for '%s'", val)
}
}
}
func TestIsCIdentifier(t *testing.T) {
goodValues := []string{
"a", "ab", "abc", "a1", "_a", "a_", "a_b", "a_1", "a__1__2__b", "__abc_123",
"A", "AB", "AbC", "A1", "_A", "A_", "A_B", "A_1", "A__1__2__B", "__123_ABC",
}
for _, val := range goodValues {
if msgs := IsCIdentifier(val); len(msgs) != 0 {
t.Errorf("expected true for '%s': %v", val, msgs)
}
}
badValues := []string{
"", "1", "123", "1a",
"-", "a-", "-a", "1-", "-1", "1_", "1_2",
".", "a.", ".a", "a.b", "1.", ".1", "1.2",
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
"#a#",
}
for _, val := range badValues {
if msgs := IsCIdentifier(val); len(msgs) == 0 {
t.Errorf("expected false for '%s'", val)
}
}
}
func TestIsValidPortNum(t *testing.T) {
goodValues := []int{1, 2, 1000, 16384, 32768, 65535}
for _, val := range goodValues {
if msgs := IsValidPortNum(val); len(msgs) != 0 {
t.Errorf("expected true for %d, got %v", val, msgs)
}
}
badValues := []int{0, -1, 65536, 100000}
for _, val := range badValues {
if msgs := IsValidPortNum(val); len(msgs) == 0 {
t.Errorf("expected false for %d", val)
}
}
}
func TestIsValidGroupId(t *testing.T) {
goodValues := []int64{0, 1, 1000, 65535, 2147483647}
for _, val := range goodValues {
if msgs := IsValidGroupId(val); len(msgs) != 0 {
t.Errorf("expected true for '%d': %v", val, msgs)
}
}
badValues := []int64{-1, -1003, 2147483648, 4147483647}
for _, val := range badValues {
if msgs := IsValidGroupId(val); len(msgs) == 0 {
t.Errorf("expected false for '%d'", val)
}
}
}
func TestIsValidUserId(t *testing.T) {
goodValues := []int64{0, 1, 1000, 65535, 2147483647}
for _, val := range goodValues {
if msgs := IsValidUserId(val); len(msgs) != 0 {
t.Errorf("expected true for '%d': %v", val, msgs)
}
}
badValues := []int64{-1, -1003, 2147483648, 4147483647}
for _, val := range badValues {
if msgs := IsValidUserId(val); len(msgs) == 0 {
t.Errorf("expected false for '%d'", val)
}
}
}
func TestIsValidPortName(t *testing.T) {
goodValues := []string{"telnet", "re-mail-ck", "pop3", "a", "a-1", "1-a", "a-1-b-2-c", "1-a-2-b-3"}
for _, val := range goodValues {
if msgs := IsValidPortName(val); len(msgs) != 0 {
t.Errorf("expected true for %q: %v", val, msgs)
}
}
badValues := []string{"longerthan15characters", "", strings.Repeat("a", 16), "12345", "1-2-3-4", "-begin", "end-", "two--hyphens", "whois++"}
for _, val := range badValues {
if msgs := IsValidPortName(val); len(msgs) == 0 {
t.Errorf("expected false for %q", val)
}
}
}
func TestIsQualifiedName(t *testing.T) {
successCases := []string{
"simple",
"now-with-dashes",
"1-starts-with-num",
"1234",
"simple/simple",
"now-with-dashes/simple",
"now-with-dashes/now-with-dashes",
"now.with.dots/simple",
"now-with.dashes-and.dots/simple",
"1-num.2-num/3-num",
"1234/5678",
"1.2.3.4/5678",
"Uppercase_Is_OK_123",
"example.com/Uppercase_Is_OK_123",
"requests.storage-foo",
strings.Repeat("a", 63),
strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63),
}
for i := range successCases {
if errs := IsQualifiedName(successCases[i]); len(errs) != 0 {
t.Errorf("case[%d]: %q: expected success: %v", i, successCases[i], errs)
}
}
errorCases := []string{
"nospecialchars%^=@",
"cantendwithadash-",
"-cantstartwithadash-",
"only/one/slash",
"Example.com/abc",
"example_com/abc",
"example.com/",
"/simple",
strings.Repeat("a", 64),
strings.Repeat("a", 254) + "/abc",
}
for i := range errorCases {
if errs := IsQualifiedName(errorCases[i]); len(errs) == 0 {
t.Errorf("case[%d]: %q: expected failure", i, errorCases[i])
}
}
}
func TestIsValidLabelValue(t *testing.T) {
successCases := []string{
"simple",
"now-with-dashes",
"1-starts-with-num",
"end-with-num-1",
"1234", // only num
strings.Repeat("a", 63), // to the limit
"", // empty value
}
for i := range successCases {
if errs := IsValidLabelValue(successCases[i]); len(errs) != 0 {
t.Errorf("case %s expected success: %v", successCases[i], errs)
}
}
errorCases := []string{
"nospecialchars%^=@",
"Tama-nui-te-rā.is.Māori.sun",
"\\backslashes\\are\\bad",
"-starts-with-dash",
"ends-with-dash-",
".starts.with.dot",
"ends.with.dot.",
strings.Repeat("a", 64), // over the limit
}
for i := range errorCases {
if errs := IsValidLabelValue(errorCases[i]); len(errs) == 0 {
t.Errorf("case[%d] expected failure", i)
}
}
}
func TestIsValidIP(t *testing.T) {
goodValues := []string{
"::1",
"2a00:79e0:2:0:f1c3:e797:93c1:df80",
"::",
"2001:4860:4860::8888",
"::fff:1.1.1.1",
"1.1.1.1",
"1.1.1.01",
"255.0.0.1",
"1.0.0.0",
"0.0.0.0",
}
for _, val := range goodValues {
if msgs := IsValidIP(val); len(msgs) != 0 {
t.Errorf("expected true for %q: %v", val, msgs)
}
}
badValues := []string{
"[2001:db8:0:1]:80",
"myhost.mydomain",
"-1.0.0.0",
"[2001:db8:0:1]",
"a",
}
for _, val := range badValues {
if msgs := IsValidIP(val); len(msgs) == 0 {
t.Errorf("expected false for %q", val)
}
}
}
func TestIsHTTPHeaderName(t *testing.T) {
goodValues := []string{
// Common ones
"Accept-Encoding", "Host", "If-Modified-Since", "X-Forwarded-For",
// Weirdo, but still conforming names
"a", "ab", "abc", "a1", "-a", "a-", "a-b", "a-1", "a--1--2--b", "--abc-123",
"A", "AB", "AbC", "A1", "-A", "A-", "A-B", "A-1", "A--1--2--B", "--123-ABC",
}
for _, val := range goodValues {
if msgs := IsHTTPHeaderName(val); len(msgs) != 0 {
t.Errorf("expected true for '%s': %v", val, msgs)
}
}
badValues := []string{
"Host:", "X-Forwarded-For:", "X-@Home",
"", "_", "a_", "_a", "1_", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2",
" ", "a ", " a", "a b", "1 ", " 1", "1 2", "#a#", "^", ",", ";", "=", "<",
"?", "@", "{",
}
for _, val := range badValues {
if msgs := IsHTTPHeaderName(val); len(msgs) == 0 {
t.Errorf("expected false for '%s'", val)
}
}
}
func TestIsValidPercent(t *testing.T) {
goodValues := []string{
"0%",
"00000%",
"1%",
"01%",
"99%",
"100%",
"101%",
}
for _, val := range goodValues {
if msgs := IsValidPercent(val); len(msgs) != 0 {
t.Errorf("expected true for %q: %v", val, msgs)
}
}
badValues := []string{
"",
"0",
"100",
"0.0%",
"99.9%",
"hundred",
" 1%",
"1% ",
"-0%",
"-1%",
"+1%",
}
for _, val := range badValues {
if msgs := IsValidPercent(val); len(msgs) == 0 {
t.Errorf("expected false for %q", val)
}
}
}
func TestIsConfigMapKey(t *testing.T) {
successCases := []string{
"a",
"good",
"good-good",
"still.good",
"this.is.also.good",
".so.is.this",
"THIS_IS_GOOD",
"so_is_this_17",
}
for i := range successCases {
if errs := IsConfigMapKey(successCases[i]); len(errs) != 0 {
t.Errorf("[%d] expected success: %v", i, errs)
}
}
failureCases := []string{
".",
"..",
"..bad",
"b*d",
"bad!&bad",
}
for i := range failureCases {
if errs := IsConfigMapKey(failureCases[i]); len(errs) == 0 {
t.Errorf("[%d] expected failure", i)
}
}
}
func TestIsWildcardDNS1123Subdomain(t *testing.T) {
goodValues := []string{
"*.example.com",
"*.bar.com",
"*.foo.bar.com",
}
for _, val := range goodValues {
if errs := IsWildcardDNS1123Subdomain(val); len(errs) != 0 {
t.Errorf("expected no errors for %q: %v", val, errs)
}
}
badValues := []string{
"*.*.bar.com",
"*.foo.*.com",
"*bar.com",
"f*.bar.com",
"*",
}
for _, val := range badValues {
if errs := IsWildcardDNS1123Subdomain(val); len(errs) == 0 {
t.Errorf("expected errors for %q", val)
}
}
}

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

@ -0,0 +1,19 @@
/*
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 wait provides tools for polling or listening for changes
// to a condition.
package wait // import "k8s.io/apimachinery/pkg/util/wait"

332
vendor/k8s.io/apimachinery/pkg/util/wait/wait.go generated vendored Normal file
View file

@ -0,0 +1,332 @@
/*
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 wait
import (
"errors"
"math/rand"
"time"
"k8s.io/apimachinery/pkg/util/runtime"
)
// For any test of the style:
// ...
// <- time.After(timeout):
// t.Errorf("Timed out")
// The value for timeout should effectively be "forever." Obviously we don't want our tests to truly lock up forever, but 30s
// is long enough that it is effectively forever for the things that can slow down a run on a heavily contended machine
// (GC, seeks, etc), but not so long as to make a developer ctrl-c a test run if they do happen to break that test.
var ForeverTestTimeout = time.Second * 30
// NeverStop may be passed to Until to make it never stop.
var NeverStop <-chan struct{} = make(chan struct{})
// Forever calls f every period for ever.
//
// Forever is syntactic sugar on top of Until.
func Forever(f func(), period time.Duration) {
Until(f, period, NeverStop)
}
// Until loops until stop channel is closed, running f every period.
//
// Until is syntactic sugar on top of JitterUntil with zero jitter factor and
// with sliding = true (which means the timer for period starts after the f
// completes).
func Until(f func(), period time.Duration, stopCh <-chan struct{}) {
JitterUntil(f, period, 0.0, true, stopCh)
}
// NonSlidingUntil loops until stop channel is closed, running f every
// period.
//
// NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter
// factor, with sliding = false (meaning the timer for period starts at the same
// time as the function starts).
func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) {
JitterUntil(f, period, 0.0, false, stopCh)
}
// JitterUntil loops until stop channel is closed, running f every period.
//
// If jitterFactor is positive, the period is jittered before every run of f.
// If jitterFactor is not positive, the period is unchanged and not jitterd.
//
// If slidingis true, the period is computed after f runs. If it is false then
// period includes the runtime for f.
//
// Close stopCh to stop. f may not be invoked if stop channel is already
// closed. Pass NeverStop to if you don't want it stop.
func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) {
for {
select {
case <-stopCh:
return
default:
}
jitteredPeriod := period
if jitterFactor > 0.0 {
jitteredPeriod = Jitter(period, jitterFactor)
}
var t *time.Timer
if !sliding {
t = time.NewTimer(jitteredPeriod)
}
func() {
defer runtime.HandleCrash()
f()
}()
if sliding {
t = time.NewTimer(jitteredPeriod)
}
// NOTE: b/c there is no priority selection in golang
// it is possible for this to race, meaning we could
// trigger t.C and stopCh, and t.C select falls through.
// In order to mitigate we re-check stopCh at the beginning
// of every loop to prevent extra executions of f().
select {
case <-stopCh:
return
case <-t.C:
}
}
}
// Jitter returns a time.Duration between duration and duration + maxFactor *
// duration.
//
// This allows clients to avoid converging on periodic behavior. If maxFactor
// is 0.0, a suggested default value will be chosen.
func Jitter(duration time.Duration, maxFactor float64) time.Duration {
if maxFactor <= 0.0 {
maxFactor = 1.0
}
wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration))
return wait
}
// ErrWaitTimeout is returned when the condition exited without success.
var ErrWaitTimeout = errors.New("timed out waiting for the condition")
// ConditionFunc returns true if the condition is satisfied, or an error
// if the loop should be aborted.
type ConditionFunc func() (done bool, err error)
// Backoff holds parameters applied to a Backoff function.
type Backoff struct {
Duration time.Duration // the base duration
Factor float64 // Duration is multipled by factor each iteration
Jitter float64 // The amount of jitter applied each iteration
Steps int // Exit with error after this many steps
}
// ExponentialBackoff repeats a condition check with exponential backoff.
//
// It checks the condition up to Steps times, increasing the wait by multipling
// the previous duration by Factor.
//
// If Jitter is greater than zero, a random amount of each duration is added
// (between duration and duration*(1+jitter)).
//
// If the condition never returns true, ErrWaitTimeout is returned. All other
// errors terminate immediately.
func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error {
duration := backoff.Duration
for i := 0; i < backoff.Steps; i++ {
if i != 0 {
adjusted := duration
if backoff.Jitter > 0.0 {
adjusted = Jitter(duration, backoff.Jitter)
}
time.Sleep(adjusted)
duration = time.Duration(float64(duration) * backoff.Factor)
}
if ok, err := condition(); err != nil || ok {
return err
}
}
return ErrWaitTimeout
}
// Poll tries a condition func until it returns true, an error, or the timeout
// is reached.
//
// Poll always waits the interval before the run of 'condition'.
// 'condition' will always be invoked at least once.
//
// Some intervals may be missed if the condition takes too long or the time
// window is too short.
//
// If you want to Poll something forever, see PollInfinite.
func Poll(interval, timeout time.Duration, condition ConditionFunc) error {
return pollInternal(poller(interval, timeout), condition)
}
func pollInternal(wait WaitFunc, condition ConditionFunc) error {
return WaitFor(wait, condition, NeverStop)
}
// PollImmediate tries a condition func until it returns true, an error, or the timeout
// is reached.
//
// Poll always checks 'condition' before waiting for the interval. 'condition'
// will always be invoked at least once.
//
// Some intervals may be missed if the condition takes too long or the time
// window is too short.
//
// If you want to Poll something forever, see PollInfinite.
func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error {
return pollImmediateInternal(poller(interval, timeout), condition)
}
func pollImmediateInternal(wait WaitFunc, condition ConditionFunc) error {
done, err := condition()
if err != nil {
return err
}
if done {
return nil
}
return pollInternal(wait, condition)
}
// PollInfinite tries a condition func until it returns true or an error
//
// PollInfinite always waits the interval before the run of 'condition'.
//
// Some intervals may be missed if the condition takes too long or the time
// window is too short.
func PollInfinite(interval time.Duration, condition ConditionFunc) error {
done := make(chan struct{})
defer close(done)
return PollUntil(interval, condition, done)
}
// PollImmediateInfinite tries a condition func until it returns true or an error
//
// PollImmediateInfinite runs the 'condition' before waiting for the interval.
//
// Some intervals may be missed if the condition takes too long or the time
// window is too short.
func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error {
done, err := condition()
if err != nil {
return err
}
if done {
return nil
}
return PollInfinite(interval, condition)
}
// PollUntil tries a condition func until it returns true, an error or stopCh is
// closed.
//
// PolUntil always waits interval before the first run of 'condition'.
// 'condition' will always be invoked at least once.
func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
return WaitFor(poller(interval, 0), condition, stopCh)
}
// WaitFunc creates a channel that receives an item every time a test
// should be executed and is closed when the last test should be invoked.
type WaitFunc func(done <-chan struct{}) <-chan struct{}
// WaitFor continually checks 'fn' as driven by 'wait'.
//
// WaitFor gets a channel from 'wait()'', and then invokes 'fn' once for every value
// placed on the channel and once more when the channel is closed.
//
// If 'fn' returns an error the loop ends and that error is returned, and if
// 'fn' returns true the loop ends and nil is returned.
//
// ErrWaitTimeout will be returned if the channel is closed without fn ever
// returning true.
func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error {
c := wait(done)
for {
_, open := <-c
ok, err := fn()
if err != nil {
return err
}
if ok {
return nil
}
if !open {
break
}
}
return ErrWaitTimeout
}
// poller returns a WaitFunc that will send to the channel every interval until
// timeout has elapsed and then closes the channel.
//
// Over very short intervals you may receive no ticks before the channel is
// closed. A timeout of 0 is interpreted as an infinity.
//
// Output ticks are not buffered. If the channel is not ready to receive an
// item, the tick is skipped.
func poller(interval, timeout time.Duration) WaitFunc {
return WaitFunc(func(done <-chan struct{}) <-chan struct{} {
ch := make(chan struct{})
go func() {
defer close(ch)
tick := time.NewTicker(interval)
defer tick.Stop()
var after <-chan time.Time
if timeout != 0 {
// time.After is more convenient, but it
// potentially leaves timers around much longer
// than necessary if we exit early.
timer := time.NewTimer(timeout)
after = timer.C
defer timer.Stop()
}
for {
select {
case <-tick.C:
// If the consumer isn't ready for this signal drop it and
// check the other channels.
select {
case ch <- struct{}{}:
default:
}
case <-after:
return
case <-done:
return
}
}
}()
return ch
})
}

501
vendor/k8s.io/apimachinery/pkg/util/wait/wait_test.go generated vendored Normal file
View file

@ -0,0 +1,501 @@
/*
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 wait
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
"k8s.io/apimachinery/pkg/util/runtime"
)
func TestUntil(t *testing.T) {
ch := make(chan struct{})
close(ch)
Until(func() {
t.Fatal("should not have been invoked")
}, 0, ch)
ch = make(chan struct{})
called := make(chan struct{})
go func() {
Until(func() {
called <- struct{}{}
}, 0, ch)
close(called)
}()
<-called
close(ch)
<-called
}
func TestNonSlidingUntil(t *testing.T) {
ch := make(chan struct{})
close(ch)
NonSlidingUntil(func() {
t.Fatal("should not have been invoked")
}, 0, ch)
ch = make(chan struct{})
called := make(chan struct{})
go func() {
NonSlidingUntil(func() {
called <- struct{}{}
}, 0, ch)
close(called)
}()
<-called
close(ch)
<-called
}
func TestUntilReturnsImmediately(t *testing.T) {
now := time.Now()
ch := make(chan struct{})
Until(func() {
close(ch)
}, 30*time.Second, ch)
if now.Add(25 * time.Second).Before(time.Now()) {
t.Errorf("Until did not return immediately when the stop chan was closed inside the func")
}
}
func TestJitterUntil(t *testing.T) {
ch := make(chan struct{})
// if a channel is closed JitterUntil never calls function f
// and returns immediately
close(ch)
JitterUntil(func() {
t.Fatal("should not have been invoked")
}, 0, 1.0, true, ch)
ch = make(chan struct{})
called := make(chan struct{})
go func() {
JitterUntil(func() {
called <- struct{}{}
}, 0, 1.0, true, ch)
close(called)
}()
<-called
close(ch)
<-called
}
func TestJitterUntilReturnsImmediately(t *testing.T) {
now := time.Now()
ch := make(chan struct{})
JitterUntil(func() {
close(ch)
}, 30*time.Second, 1.0, true, ch)
if now.Add(25 * time.Second).Before(time.Now()) {
t.Errorf("JitterUntil did not return immediately when the stop chan was closed inside the func")
}
}
func TestJitterUntilRecoversPanic(t *testing.T) {
// Save and restore crash handlers
originalReallyCrash := runtime.ReallyCrash
originalHandlers := runtime.PanicHandlers
defer func() {
runtime.ReallyCrash = originalReallyCrash
runtime.PanicHandlers = originalHandlers
}()
called := 0
handled := 0
// Hook up a custom crash handler to ensure it is called when a jitter function panics
runtime.ReallyCrash = false
runtime.PanicHandlers = []func(interface{}){
func(p interface{}) {
handled++
},
}
ch := make(chan struct{})
JitterUntil(func() {
called++
if called > 2 {
close(ch)
return
}
panic("TestJitterUntilRecoversPanic")
}, time.Millisecond, 1.0, true, ch)
if called != 3 {
t.Errorf("Expected panic recovers")
}
}
func TestJitterUntilNegativeFactor(t *testing.T) {
now := time.Now()
ch := make(chan struct{})
called := make(chan struct{})
received := make(chan struct{})
go func() {
JitterUntil(func() {
called <- struct{}{}
<-received
}, time.Second, -30.0, true, ch)
}()
// first loop
<-called
received <- struct{}{}
// second loop
<-called
close(ch)
received <- struct{}{}
// it should take at most 2 seconds + some overhead, not 3
if now.Add(3 * time.Second).Before(time.Now()) {
t.Errorf("JitterUntil did not returned after predefined period with negative jitter factor when the stop chan was closed inside the func")
}
}
func TestExponentialBackoff(t *testing.T) {
opts := Backoff{Factor: 1.0, Steps: 3}
// waits up to steps
i := 0
err := ExponentialBackoff(opts, func() (bool, error) {
i++
return false, nil
})
if err != ErrWaitTimeout || i != opts.Steps {
t.Errorf("unexpected error: %v", err)
}
// returns immediately
i = 0
err = ExponentialBackoff(opts, func() (bool, error) {
i++
return true, nil
})
if err != nil || i != 1 {
t.Errorf("unexpected error: %v", err)
}
// returns immediately on error
testErr := fmt.Errorf("some other error")
err = ExponentialBackoff(opts, func() (bool, error) {
return false, testErr
})
if err != testErr {
t.Errorf("unexpected error: %v", err)
}
// invoked multiple times
i = 1
err = ExponentialBackoff(opts, func() (bool, error) {
if i < opts.Steps {
i++
return false, nil
}
return true, nil
})
if err != nil || i != opts.Steps {
t.Errorf("unexpected error: %v", err)
}
}
func TestPoller(t *testing.T) {
done := make(chan struct{})
defer close(done)
w := poller(time.Millisecond, 2*time.Millisecond)
ch := w(done)
count := 0
DRAIN:
for {
select {
case _, open := <-ch:
if !open {
break DRAIN
}
count++
case <-time.After(ForeverTestTimeout):
t.Errorf("unexpected timeout after poll")
}
}
if count > 3 {
t.Errorf("expected up to three values, got %d", count)
}
}
type fakePoller struct {
max int
used int32 // accessed with atomics
wg sync.WaitGroup
}
func fakeTicker(max int, used *int32, doneFunc func()) WaitFunc {
return func(done <-chan struct{}) <-chan struct{} {
ch := make(chan struct{})
go func() {
defer doneFunc()
defer close(ch)
for i := 0; i < max; i++ {
select {
case ch <- struct{}{}:
case <-done:
return
}
if used != nil {
atomic.AddInt32(used, 1)
}
}
}()
return ch
}
}
func (fp *fakePoller) GetWaitFunc() WaitFunc {
fp.wg.Add(1)
return fakeTicker(fp.max, &fp.used, fp.wg.Done)
}
func TestPoll(t *testing.T) {
invocations := 0
f := ConditionFunc(func() (bool, error) {
invocations++
return true, nil
})
fp := fakePoller{max: 1}
if err := pollInternal(fp.GetWaitFunc(), f); err != nil {
t.Fatalf("unexpected error %v", err)
}
fp.wg.Wait()
if invocations != 1 {
t.Errorf("Expected exactly one invocation, got %d", invocations)
}
used := atomic.LoadInt32(&fp.used)
if used != 1 {
t.Errorf("Expected exactly one tick, got %d", used)
}
}
func TestPollError(t *testing.T) {
expectedError := errors.New("Expected error")
f := ConditionFunc(func() (bool, error) {
return false, expectedError
})
fp := fakePoller{max: 1}
if err := pollInternal(fp.GetWaitFunc(), f); err == nil || err != expectedError {
t.Fatalf("Expected error %v, got none %v", expectedError, err)
}
fp.wg.Wait()
used := atomic.LoadInt32(&fp.used)
if used != 1 {
t.Errorf("Expected exactly one tick, got %d", used)
}
}
func TestPollImmediate(t *testing.T) {
invocations := 0
f := ConditionFunc(func() (bool, error) {
invocations++
return true, nil
})
fp := fakePoller{max: 0}
if err := pollImmediateInternal(fp.GetWaitFunc(), f); err != nil {
t.Fatalf("unexpected error %v", err)
}
// We don't need to wait for fp.wg, as pollImmediate shouldn't call WaitFunc at all.
if invocations != 1 {
t.Errorf("Expected exactly one invocation, got %d", invocations)
}
used := atomic.LoadInt32(&fp.used)
if used != 0 {
t.Errorf("Expected exactly zero ticks, got %d", used)
}
}
func TestPollImmediateError(t *testing.T) {
expectedError := errors.New("Expected error")
f := ConditionFunc(func() (bool, error) {
return false, expectedError
})
fp := fakePoller{max: 0}
if err := pollImmediateInternal(fp.GetWaitFunc(), f); err == nil || err != expectedError {
t.Fatalf("Expected error %v, got none %v", expectedError, err)
}
// We don't need to wait for fp.wg, as pollImmediate shouldn't call WaitFunc at all.
used := atomic.LoadInt32(&fp.used)
if used != 0 {
t.Errorf("Expected exactly zero ticks, got %d", used)
}
}
func TestPollForever(t *testing.T) {
ch := make(chan struct{})
done := make(chan struct{}, 1)
complete := make(chan struct{})
go func() {
f := ConditionFunc(func() (bool, error) {
ch <- struct{}{}
select {
case <-done:
return true, nil
default:
}
return false, nil
})
if err := PollInfinite(time.Microsecond, f); err != nil {
t.Fatalf("unexpected error %v", err)
}
close(ch)
complete <- struct{}{}
}()
// ensure the condition is opened
<-ch
// ensure channel sends events
for i := 0; i < 10; i++ {
select {
case _, open := <-ch:
if !open {
t.Fatalf("did not expect channel to be closed")
}
case <-time.After(ForeverTestTimeout):
t.Fatalf("channel did not return at least once within the poll interval")
}
}
// at most one poll notification should be sent once we return from the condition
done <- struct{}{}
go func() {
for i := 0; i < 2; i++ {
_, open := <-ch
if !open {
return
}
}
t.Fatalf("expected closed channel after two iterations")
}()
<-complete
}
func TestWaitFor(t *testing.T) {
var invocations int
testCases := map[string]struct {
F ConditionFunc
Ticks int
Invoked int
Err bool
}{
"invoked once": {
ConditionFunc(func() (bool, error) {
invocations++
return true, nil
}),
2,
1,
false,
},
"invoked and returns a timeout": {
ConditionFunc(func() (bool, error) {
invocations++
return false, nil
}),
2,
3, // the contract of WaitFor() says the func is called once more at the end of the wait
true,
},
"returns immediately on error": {
ConditionFunc(func() (bool, error) {
invocations++
return false, errors.New("test")
}),
2,
1,
true,
},
}
for k, c := range testCases {
invocations = 0
ticker := fakeTicker(c.Ticks, nil, func() {})
err := func() error {
done := make(chan struct{})
defer close(done)
return WaitFor(ticker, c.F, done)
}()
switch {
case c.Err && err == nil:
t.Errorf("%s: Expected error, got nil", k)
continue
case !c.Err && err != nil:
t.Errorf("%s: Expected no error, got: %#v", k, err)
continue
}
if invocations != c.Invoked {
t.Errorf("%s: Expected %d invocations, got %d", k, c.Invoked, invocations)
}
}
}
func TestWaitForWithDelay(t *testing.T) {
done := make(chan struct{})
defer close(done)
WaitFor(poller(time.Millisecond, ForeverTestTimeout), func() (bool, error) {
time.Sleep(10 * time.Millisecond)
return true, nil
}, done)
// If polling goroutine doesn't see the done signal it will leak timers.
select {
case done <- struct{}{}:
case <-time.After(ForeverTestTimeout):
t.Errorf("expected an ack of the done signal.")
}
}
func TestPollUntil(t *testing.T) {
stopCh := make(chan struct{})
called := make(chan bool)
pollDone := make(chan struct{})
go func() {
PollUntil(time.Microsecond, ConditionFunc(func() (bool, error) {
called <- true
return false, nil
}), stopCh)
close(pollDone)
}()
// make sure we're called once
<-called
// this should trigger a "done"
close(stopCh)
go func() {
// release the condition func if needed
for {
<-called
}
}()
// make sure we finished the poll
<-pollDone
}

346
vendor/k8s.io/apimachinery/pkg/util/yaml/decoder.go generated vendored Normal file
View file

@ -0,0 +1,346 @@
/*
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 yaml
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strings"
"unicode"
"github.com/ghodss/yaml"
"github.com/golang/glog"
)
// ToJSON converts a single YAML document into a JSON document
// or returns an error. If the document appears to be JSON the
// YAML decoding path is not used (so that error messages are
// JSON specific).
func ToJSON(data []byte) ([]byte, error) {
if hasJSONPrefix(data) {
return data, nil
}
return yaml.YAMLToJSON(data)
}
// YAMLToJSONDecoder decodes YAML documents from an io.Reader by
// separating individual documents. It first converts the YAML
// body to JSON, then unmarshals the JSON.
type YAMLToJSONDecoder struct {
reader Reader
}
// NewYAMLToJSONDecoder decodes YAML documents from the provided
// stream in chunks by converting each document (as defined by
// the YAML spec) into its own chunk, converting it to JSON via
// yaml.YAMLToJSON, and then passing it to json.Decoder.
func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder {
reader := bufio.NewReader(r)
return &YAMLToJSONDecoder{
reader: NewYAMLReader(reader),
}
}
// Decode reads a YAML document as JSON from the stream or returns
// an error. The decoding rules match json.Unmarshal, not
// yaml.Unmarshal.
func (d *YAMLToJSONDecoder) Decode(into interface{}) error {
bytes, err := d.reader.Read()
if err != nil && err != io.EOF {
return err
}
if len(bytes) != 0 {
err := yaml.Unmarshal(bytes, into)
if err != nil {
return YAMLSyntaxError{err}
}
}
return err
}
// YAMLDecoder reads chunks of objects and returns ErrShortBuffer if
// the data is not sufficient.
type YAMLDecoder struct {
r io.ReadCloser
scanner *bufio.Scanner
remaining []byte
}
// NewDocumentDecoder decodes YAML documents from the provided
// stream in chunks by converting each document (as defined by
// the YAML spec) into its own chunk. io.ErrShortBuffer will be
// returned if the entire buffer could not be read to assist
// the caller in framing the chunk.
func NewDocumentDecoder(r io.ReadCloser) io.ReadCloser {
scanner := bufio.NewScanner(r)
scanner.Split(splitYAMLDocument)
return &YAMLDecoder{
r: r,
scanner: scanner,
}
}
// Read reads the previous slice into the buffer, or attempts to read
// the next chunk.
// TODO: switch to readline approach.
func (d *YAMLDecoder) Read(data []byte) (n int, err error) {
left := len(d.remaining)
if left == 0 {
// return the next chunk from the stream
if !d.scanner.Scan() {
err := d.scanner.Err()
if err == nil {
err = io.EOF
}
return 0, err
}
out := d.scanner.Bytes()
d.remaining = out
left = len(out)
}
// fits within data
if left <= len(data) {
copy(data, d.remaining)
d.remaining = nil
return len(d.remaining), nil
}
// caller will need to reread
copy(data, d.remaining[:left])
d.remaining = d.remaining[left:]
return len(data), io.ErrShortBuffer
}
func (d *YAMLDecoder) Close() error {
return d.r.Close()
}
const yamlSeparator = "\n---"
const separator = "---"
// splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents.
func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
sep := len([]byte(yamlSeparator))
if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 {
// We have a potential document terminator
i += sep
after := data[i:]
if len(after) == 0 {
// we can't read any more characters
if atEOF {
return len(data), data[:len(data)-sep], nil
}
return 0, nil, nil
}
if j := bytes.IndexByte(after, '\n'); j >= 0 {
return i + j + 1, data[0 : i-sep], nil
}
return 0, nil, nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
// decoder is a convenience interface for Decode.
type decoder interface {
Decode(into interface{}) error
}
// YAMLOrJSONDecoder attempts to decode a stream of JSON documents or
// YAML documents by sniffing for a leading { character.
type YAMLOrJSONDecoder struct {
r io.Reader
bufferSize int
decoder decoder
rawData []byte
}
type JSONSyntaxError struct {
Line int
Err error
}
func (e JSONSyntaxError) Error() string {
return fmt.Sprintf("json: line %d: %s", e.Line, e.Err.Error())
}
type YAMLSyntaxError struct {
err error
}
func (e YAMLSyntaxError) Error() string {
return e.err.Error()
}
// NewYAMLOrJSONDecoder returns a decoder that will process YAML documents
// or JSON documents from the given reader as a stream. bufferSize determines
// how far into the stream the decoder will look to figure out whether this
// is a JSON stream (has whitespace followed by an open brace).
func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder {
return &YAMLOrJSONDecoder{
r: r,
bufferSize: bufferSize,
}
}
// Decode unmarshals the next object from the underlying stream into the
// provide object, or returns an error.
func (d *YAMLOrJSONDecoder) Decode(into interface{}) error {
if d.decoder == nil {
buffer, origData, isJSON := GuessJSONStream(d.r, d.bufferSize)
if isJSON {
glog.V(4).Infof("decoding stream as JSON")
d.decoder = json.NewDecoder(buffer)
d.rawData = origData
} else {
glog.V(4).Infof("decoding stream as YAML")
d.decoder = NewYAMLToJSONDecoder(buffer)
}
}
err := d.decoder.Decode(into)
if jsonDecoder, ok := d.decoder.(*json.Decoder); ok {
if syntax, ok := err.(*json.SyntaxError); ok {
data, readErr := ioutil.ReadAll(jsonDecoder.Buffered())
if readErr != nil {
glog.V(4).Infof("reading stream failed: %v", readErr)
}
js := string(data)
// if contents from io.Reader are not complete,
// use the original raw data to prevent panic
if int64(len(js)) <= syntax.Offset {
js = string(d.rawData)
}
start := strings.LastIndex(js[:syntax.Offset], "\n") + 1
line := strings.Count(js[:start], "\n")
return JSONSyntaxError{
Line: line,
Err: fmt.Errorf(syntax.Error()),
}
}
}
return err
}
type Reader interface {
Read() ([]byte, error)
}
type YAMLReader struct {
reader Reader
}
func NewYAMLReader(r *bufio.Reader) *YAMLReader {
return &YAMLReader{
reader: &LineReader{reader: r},
}
}
// Read returns a full YAML document.
func (r *YAMLReader) Read() ([]byte, error) {
var buffer bytes.Buffer
for {
line, err := r.reader.Read()
if err != nil && err != io.EOF {
return nil, err
}
sep := len([]byte(separator))
if i := bytes.Index(line, []byte(separator)); i == 0 {
// We have a potential document terminator
i += sep
after := line[i:]
if len(strings.TrimRightFunc(string(after), unicode.IsSpace)) == 0 {
if buffer.Len() != 0 {
return buffer.Bytes(), nil
}
if err == io.EOF {
return nil, err
}
}
}
if err == io.EOF {
if buffer.Len() != 0 {
// If we're at EOF, we have a final, non-terminated line. Return it.
return buffer.Bytes(), nil
}
return nil, err
}
buffer.Write(line)
}
}
type LineReader struct {
reader *bufio.Reader
}
// Read returns a single line (with '\n' ended) from the underlying reader.
// An error is returned iff there is an error with the underlying reader.
func (r *LineReader) Read() ([]byte, error) {
var (
isPrefix bool = true
err error = nil
line []byte
buffer bytes.Buffer
)
for isPrefix && err == nil {
line, isPrefix, err = r.reader.ReadLine()
buffer.Write(line)
}
buffer.WriteByte('\n')
return buffer.Bytes(), err
}
// GuessJSONStream scans the provided reader up to size, looking
// for an open brace indicating this is JSON. It will return the
// bufio.Reader it creates for the consumer.
func GuessJSONStream(r io.Reader, size int) (io.Reader, []byte, bool) {
buffer := bufio.NewReaderSize(r, size)
b, _ := buffer.Peek(size)
return buffer, b, hasJSONPrefix(b)
}
var jsonPrefix = []byte("{")
// hasJSONPrefix returns true if the provided buffer appears to start with
// a JSON open brace.
func hasJSONPrefix(buf []byte) bool {
return hasPrefix(buf, jsonPrefix)
}
// Return true if the first non-whitespace bytes in buf is
// prefix.
func hasPrefix(buf []byte, prefix []byte) bool {
trim := bytes.TrimLeftFunc(buf, unicode.IsSpace)
return bytes.HasPrefix(trim, prefix)
}

View file

@ -0,0 +1,349 @@
/*
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 yaml
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"reflect"
"strings"
"testing"
)
func TestSplitYAMLDocument(t *testing.T) {
testCases := []struct {
input string
atEOF bool
expect string
adv int
}{
{"foo", true, "foo", 3},
{"fo", false, "", 0},
{"---", true, "---", 3},
{"---\n", true, "---\n", 4},
{"---\n", false, "", 0},
{"\n---\n", false, "", 5},
{"\n---\n", true, "", 5},
{"abc\n---\ndef", true, "abc", 8},
{"def", true, "def", 3},
{"", true, "", 0},
}
for i, testCase := range testCases {
adv, token, err := splitYAMLDocument([]byte(testCase.input), testCase.atEOF)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
if adv != testCase.adv {
t.Errorf("%d: advance did not match: %d %d", i, testCase.adv, adv)
}
if testCase.expect != string(token) {
t.Errorf("%d: token did not match: %q %q", i, testCase.expect, string(token))
}
}
}
func TestGuessJSON(t *testing.T) {
if r, _, isJSON := GuessJSONStream(bytes.NewReader([]byte(" \n{}")), 100); !isJSON {
t.Fatalf("expected stream to be JSON")
} else {
b := make([]byte, 30)
n, err := r.Read(b)
if err != nil || n != 4 {
t.Fatalf("unexpected body: %d / %v", n, err)
}
if string(b[:n]) != " \n{}" {
t.Fatalf("unexpected body: %q", string(b[:n]))
}
}
}
func TestScanYAML(t *testing.T) {
s := bufio.NewScanner(bytes.NewReader([]byte(`---
stuff: 1
---
`)))
s.Split(splitYAMLDocument)
if !s.Scan() {
t.Fatalf("should have been able to scan")
}
t.Logf("scan: %s", s.Text())
if !s.Scan() {
t.Fatalf("should have been able to scan")
}
t.Logf("scan: %s", s.Text())
if s.Scan() {
t.Fatalf("scan should have been done")
}
if s.Err() != nil {
t.Fatalf("err should have been nil: %v", s.Err())
}
}
func TestDecodeYAML(t *testing.T) {
s := NewYAMLToJSONDecoder(bytes.NewReader([]byte(`---
stuff: 1
---
`)))
obj := generic{}
if err := s.Decode(&obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if fmt.Sprintf("%#v", obj) != `yaml.generic{"stuff":1}` {
t.Errorf("unexpected object: %#v", obj)
}
obj = generic{}
if err := s.Decode(&obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(obj) != 0 {
t.Fatalf("unexpected object: %#v", obj)
}
obj = generic{}
if err := s.Decode(&obj); err != io.EOF {
t.Fatalf("unexpected error: %v", err)
}
}
func TestDecodeBrokenYAML(t *testing.T) {
s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`---
stuff: 1
test-foo: 1
---
`)), 100)
obj := generic{}
err := s.Decode(&obj)
if err == nil {
t.Fatal("expected error with yaml: violate, got no error")
}
fmt.Printf("err: %s\n", err.Error())
if !strings.Contains(err.Error(), "yaml: line 2:") {
t.Fatalf("expected %q to have 'yaml: line 2:' found a tab character", err.Error())
}
}
func TestDecodeBrokenJSON(t *testing.T) {
s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`{
"foo": {
"stuff": 1
"otherStuff": 2
}
}
`)), 100)
obj := generic{}
err := s.Decode(&obj)
if err == nil {
t.Fatal("expected error with json: prefix, got no error")
}
if !strings.HasPrefix(err.Error(), "json: line 3:") {
t.Fatalf("expected %q to have 'json: line 3:' prefix", err.Error())
}
}
type generic map[string]interface{}
func TestYAMLOrJSONDecoder(t *testing.T) {
testCases := []struct {
input string
buffer int
isJSON bool
err bool
out []generic
}{
{` {"1":2}{"3":4}`, 2, true, false, []generic{
{"1": 2},
{"3": 4},
}},
{" \n{}", 3, true, false, []generic{
{},
}},
{" \na: b", 2, false, false, []generic{
{"a": "b"},
}},
{" \n{\"a\": \"b\"}", 2, false, true, []generic{
{"a": "b"},
}},
{" \n{\"a\": \"b\"}", 3, true, false, []generic{
{"a": "b"},
}},
{` {"a":"b"}`, 100, true, false, []generic{
{"a": "b"},
}},
{"", 1, false, false, []generic{}},
{"foo: bar\n---\nbaz: biz", 100, false, false, []generic{
{"foo": "bar"},
{"baz": "biz"},
}},
{"foo: bar\n---\n", 100, false, false, []generic{
{"foo": "bar"},
}},
{"foo: bar\n---", 100, false, false, []generic{
{"foo": "bar"},
}},
{"foo: bar\n--", 100, false, true, []generic{
{"foo": "bar"},
}},
{"foo: bar\n-", 100, false, true, []generic{
{"foo": "bar"},
}},
{"foo: bar\n", 100, false, false, []generic{
{"foo": "bar"},
}},
}
for i, testCase := range testCases {
decoder := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(testCase.input)), testCase.buffer)
objs := []generic{}
var err error
for {
out := make(generic)
err = decoder.Decode(&out)
if err != nil {
break
}
objs = append(objs, out)
}
if err != io.EOF {
switch {
case testCase.err && err == nil:
t.Errorf("%d: unexpected non-error", i)
continue
case !testCase.err && err != nil:
t.Errorf("%d: unexpected error: %v", i, err)
continue
case err != nil:
continue
}
}
switch decoder.decoder.(type) {
case *YAMLToJSONDecoder:
if testCase.isJSON {
t.Errorf("%d: expected JSON decoder, got YAML", i)
}
case *json.Decoder:
if !testCase.isJSON {
t.Errorf("%d: expected YAML decoder, got JSON", i)
}
}
if fmt.Sprintf("%#v", testCase.out) != fmt.Sprintf("%#v", objs) {
t.Errorf("%d: objects were not equal: \n%#v\n%#v", i, testCase.out, objs)
}
}
}
func TestReadSingleLongLine(t *testing.T) {
testReadLines(t, []int{128 * 1024})
}
func TestReadRandomLineLengths(t *testing.T) {
minLength := 100
maxLength := 96 * 1024
maxLines := 100
lineLengths := make([]int, maxLines)
for i := 0; i < maxLines; i++ {
lineLengths[i] = rand.Intn(maxLength-minLength) + minLength
}
testReadLines(t, lineLengths)
}
func testReadLines(t *testing.T, lineLengths []int) {
var (
lines [][]byte
inputStream []byte
)
for _, lineLength := range lineLengths {
inputLine := make([]byte, lineLength+1)
for i := 0; i < lineLength; i++ {
char := rand.Intn('z'-'A') + 'A'
inputLine[i] = byte(char)
}
inputLine[len(inputLine)-1] = '\n'
lines = append(lines, inputLine)
}
for _, line := range lines {
inputStream = append(inputStream, line...)
}
// init Reader
reader := bufio.NewReader(bytes.NewReader(inputStream))
lineReader := &LineReader{reader: reader}
// read lines
var readLines [][]byte
for range lines {
bytes, err := lineReader.Read()
if err != nil && err != io.EOF {
t.Fatalf("failed to read lines: %v", err)
}
readLines = append(readLines, bytes)
}
// validate
for i := range lines {
if len(lines[i]) != len(readLines[i]) {
t.Fatalf("expected line length: %d, but got %d", len(lines[i]), len(readLines[i]))
}
if !reflect.DeepEqual(lines[i], readLines[i]) {
t.Fatalf("expected line: %v, but got %v", lines[i], readLines[i])
}
}
}
func TestTypedJSONOrYamlErrors(t *testing.T) {
s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`{
"foo": {
"stuff": 1
"otherStuff": 2
}
}
`)), 100)
obj := generic{}
err := s.Decode(&obj)
if err == nil {
t.Fatal("expected error with json: prefix, got no error")
}
if _, ok := err.(JSONSyntaxError); !ok {
t.Fatalf("expected %q to be of type JSONSyntaxError", err.Error())
}
s = NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`---
stuff: 1
test-foo: 1
---
`)), 100)
obj = generic{}
err = s.Decode(&obj)
if err == nil {
t.Fatal("expected error with yaml: prefix, got no error")
}
if _, ok := err.(YAMLSyntaxError); !ok {
t.Fatalf("expected %q to be of type YAMLSyntaxError", err.Error())
}
}