Replace godep with dep

This commit is contained in:
Manuel de Brito Fontes 2017-10-06 17:26:14 -03:00
parent 1e7489927c
commit bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions

View file

@ -0,0 +1,169 @@
/*
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 v1_test
import (
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testing/compat"
"k8s.io/kubernetes/pkg/api/validation"
_ "k8s.io/kubernetes/pkg/api/install"
)
func TestCompatibility_v1_PodSecurityContext(t *testing.T) {
cases := []struct {
name string
input string
expectedKeys map[string]string
absentKeys []string
}{
{
name: "hostNetwork = true",
input: `
{
"kind":"Pod",
"apiVersion":"v1",
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
"spec": {
"hostNetwork": true,
"containers":[{
"name":"a",
"image":"my-container-image"
}]
}
}
`,
expectedKeys: map[string]string{
"spec.hostNetwork": "true",
},
},
{
name: "hostNetwork = false",
input: `
{
"kind":"Pod",
"apiVersion":"v1",
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
"spec": {
"hostNetwork": false,
"containers":[{
"name":"a",
"image":"my-container-image"
}]
}
}
`,
absentKeys: []string{
"spec.hostNetwork",
},
},
{
name: "hostIPC = true",
input: `
{
"kind":"Pod",
"apiVersion":"v1",
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
"spec": {
"hostIPC": true,
"containers":[{
"name":"a",
"image":"my-container-image"
}]
}
}
`,
expectedKeys: map[string]string{
"spec.hostIPC": "true",
},
},
{
name: "hostIPC = false",
input: `
{
"kind":"Pod",
"apiVersion":"v1",
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
"spec": {
"hostIPC": false,
"containers":[{
"name":"a",
"image":"my-container-image"
}]
}
}
`,
absentKeys: []string{
"spec.hostIPC",
},
},
{
name: "hostPID = true",
input: `
{
"kind":"Pod",
"apiVersion":"v1",
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
"spec": {
"hostPID": true,
"containers":[{
"name":"a",
"image":"my-container-image"
}]
}
}
`,
expectedKeys: map[string]string{
"spec.hostPID": "true",
},
},
{
name: "hostPID = false",
input: `
{
"kind":"Pod",
"apiVersion":"v1",
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
"spec": {
"hostPID": false,
"containers":[{
"name":"a",
"image":"my-container-image"
}]
}
}
`,
absentKeys: []string{
"spec.hostPID",
},
},
}
validator := func(obj runtime.Object) field.ErrorList {
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), field.NewPath("spec"))
}
for _, tc := range cases {
t.Logf("Testing 1.0.0 backward compatibility for %v", tc.name)
compat.TestCompatibility(t, v1.SchemeGroupVersion, []byte(tc.input), validator, tc.expectedKeys, tc.absentKeys)
}
}

224
vendor/k8s.io/kubernetes/pkg/api/v1/conversion_test.go generated vendored Normal file
View file

@ -0,0 +1,224 @@
/*
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 v1_test
import (
"net/url"
"reflect"
"testing"
"time"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/api"
k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
)
func TestPodLogOptions(t *testing.T) {
sinceSeconds := int64(1)
sinceTime := metav1.NewTime(time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC).Local())
tailLines := int64(2)
limitBytes := int64(3)
versionedLogOptions := &v1.PodLogOptions{
Container: "mycontainer",
Follow: true,
Previous: true,
SinceSeconds: &sinceSeconds,
SinceTime: &sinceTime,
Timestamps: true,
TailLines: &tailLines,
LimitBytes: &limitBytes,
}
unversionedLogOptions := &api.PodLogOptions{
Container: "mycontainer",
Follow: true,
Previous: true,
SinceSeconds: &sinceSeconds,
SinceTime: &sinceTime,
Timestamps: true,
TailLines: &tailLines,
LimitBytes: &limitBytes,
}
expectedParameters := url.Values{
"container": {"mycontainer"},
"follow": {"true"},
"previous": {"true"},
"sinceSeconds": {"1"},
"sinceTime": {"2000-01-01T12:34:56Z"},
"timestamps": {"true"},
"tailLines": {"2"},
"limitBytes": {"3"},
}
codec := runtime.NewParameterCodec(api.Scheme)
// unversioned -> query params
{
actualParameters, err := codec.EncodeParameters(unversionedLogOptions, v1.SchemeGroupVersion)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(actualParameters, expectedParameters) {
t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters)
}
}
// versioned -> query params
{
actualParameters, err := codec.EncodeParameters(versionedLogOptions, v1.SchemeGroupVersion)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(actualParameters, expectedParameters) {
t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters)
}
}
// query params -> versioned
{
convertedLogOptions := &v1.PodLogOptions{}
err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(convertedLogOptions, versionedLogOptions) {
t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(versionedLogOptions, convertedLogOptions))
}
}
// query params -> unversioned
{
convertedLogOptions := &api.PodLogOptions{}
err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(convertedLogOptions, unversionedLogOptions) {
t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(unversionedLogOptions, convertedLogOptions))
}
}
}
// TestPodSpecConversion tests that v1.ServiceAccount is an alias for
// ServiceAccountName.
func TestPodSpecConversion(t *testing.T) {
name, other := "foo", "bar"
// Test internal -> v1. Should have both alias (DeprecatedServiceAccount)
// and new field (ServiceAccountName).
i := &api.PodSpec{
ServiceAccountName: name,
}
v := v1.PodSpec{}
if err := api.Scheme.Convert(i, &v, nil); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if v.ServiceAccountName != name {
t.Fatalf("want v1.ServiceAccountName %q, got %q", name, v.ServiceAccountName)
}
if v.DeprecatedServiceAccount != name {
t.Fatalf("want v1.DeprecatedServiceAccount %q, got %q", name, v.DeprecatedServiceAccount)
}
// Test v1 -> internal. Either DeprecatedServiceAccount, ServiceAccountName,
// or both should translate to ServiceAccountName. ServiceAccountName wins
// if both are set.
testCases := []*v1.PodSpec{
// New
{ServiceAccountName: name},
// Alias
{DeprecatedServiceAccount: name},
// Both: same
{ServiceAccountName: name, DeprecatedServiceAccount: name},
// Both: different
{ServiceAccountName: name, DeprecatedServiceAccount: other},
}
for k, v := range testCases {
got := api.PodSpec{}
err := api.Scheme.Convert(v, &got, nil)
if err != nil {
t.Fatalf("unexpected error for case %d: %v", k, err)
}
if got.ServiceAccountName != name {
t.Fatalf("want api.ServiceAccountName %q, got %q", name, got.ServiceAccountName)
}
}
}
func TestResourceListConversion(t *testing.T) {
bigMilliQuantity := resource.NewQuantity(resource.MaxMilliValue, resource.DecimalSI)
bigMilliQuantity.Add(resource.MustParse("12345m"))
tests := []struct {
input v1.ResourceList
expected api.ResourceList
}{
{ // No changes necessary.
input: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("30M"),
v1.ResourceCPU: resource.MustParse("100m"),
v1.ResourceStorage: resource.MustParse("1G"),
},
expected: api.ResourceList{
api.ResourceMemory: resource.MustParse("30M"),
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceStorage: resource.MustParse("1G"),
},
},
{ // Nano-scale values should be rounded up to milli-scale.
input: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("3.000023m"),
v1.ResourceMemory: resource.MustParse("500.000050m"),
},
expected: api.ResourceList{
api.ResourceCPU: resource.MustParse("4m"),
api.ResourceMemory: resource.MustParse("501m"),
},
},
{ // Large values should still be accurate.
input: v1.ResourceList{
v1.ResourceCPU: *bigMilliQuantity.Copy(),
v1.ResourceStorage: *bigMilliQuantity.Copy(),
},
expected: api.ResourceList{
api.ResourceCPU: *bigMilliQuantity.Copy(),
api.ResourceStorage: *bigMilliQuantity.Copy(),
},
},
}
for i, test := range tests {
output := api.ResourceList{}
// defaulting is a separate step from conversion that is applied when reading from the API or from etcd.
// perform that step explicitly.
k8s_api_v1.SetDefaults_ResourceList(&test.input)
err := api.Scheme.Convert(&test.input, &output, nil)
if err != nil {
t.Fatalf("unexpected error for case %d: %v", i, err)
}
if !apiequality.Semantic.DeepEqual(test.expected, output) {
t.Errorf("unexpected conversion for case %d: Expected\n%+v;\nGot\n%+v", i, test.expected, output)
}
}
}

1334
vendor/k8s.io/kubernetes/pkg/api/v1/defaults_test.go generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -20,4 +20,4 @@ limitations under the License.
// +k8s:defaulter-gen-input=../../../vendor/k8s.io/api/core/v1
// Package v1 is the v1 version of the API.
package v1
package v1 // import "k8s.io/kubernetes/pkg/api/v1"

41
vendor/k8s.io/kubernetes/pkg/api/v1/endpoints/BUILD generated vendored Normal file
View file

@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["util.go"],
deps = [
"//pkg/util/hash:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
library = ":go_default_library",
deps = [
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

226
vendor/k8s.io/kubernetes/pkg/api/v1/endpoints/util.go generated vendored Normal file
View file

@ -0,0 +1,226 @@
/*
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 endpoints
import (
"bytes"
"crypto/md5"
"encoding/hex"
"hash"
"sort"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
hashutil "k8s.io/kubernetes/pkg/util/hash"
)
// RepackSubsets takes a slice of EndpointSubset objects, expands it to the full
// representation, and then repacks that into the canonical layout. This
// ensures that code which operates on these objects can rely on the common
// form for things like comparison. The result is a newly allocated slice.
func RepackSubsets(subsets []v1.EndpointSubset) []v1.EndpointSubset {
// First map each unique port definition to the sets of hosts that
// offer it.
allAddrs := map[addressKey]*v1.EndpointAddress{}
portToAddrReadyMap := map[v1.EndpointPort]addressSet{}
for i := range subsets {
for _, port := range subsets[i].Ports {
for k := range subsets[i].Addresses {
mapAddressByPort(&subsets[i].Addresses[k], port, true, allAddrs, portToAddrReadyMap)
}
for k := range subsets[i].NotReadyAddresses {
mapAddressByPort(&subsets[i].NotReadyAddresses[k], port, false, allAddrs, portToAddrReadyMap)
}
}
}
// Next, map the sets of hosts to the sets of ports they offer.
// Go does not allow maps or slices as keys to maps, so we have
// to synthesize an artificial key and do a sort of 2-part
// associative entity.
type keyString string
keyToAddrReadyMap := map[keyString]addressSet{}
addrReadyMapKeyToPorts := map[keyString][]v1.EndpointPort{}
for port, addrs := range portToAddrReadyMap {
key := keyString(hashAddresses(addrs))
keyToAddrReadyMap[key] = addrs
addrReadyMapKeyToPorts[key] = append(addrReadyMapKeyToPorts[key], port)
}
// Next, build the N-to-M association the API wants.
final := []v1.EndpointSubset{}
for key, ports := range addrReadyMapKeyToPorts {
var readyAddrs, notReadyAddrs []v1.EndpointAddress
for addr, ready := range keyToAddrReadyMap[key] {
if ready {
readyAddrs = append(readyAddrs, *addr)
} else {
notReadyAddrs = append(notReadyAddrs, *addr)
}
}
final = append(final, v1.EndpointSubset{Addresses: readyAddrs, NotReadyAddresses: notReadyAddrs, Ports: ports})
}
// Finally, sort it.
return SortSubsets(final)
}
// The sets of hosts must be de-duped, using IP+UID as the key.
type addressKey struct {
ip string
uid types.UID
}
// mapAddressByPort adds an address into a map by its ports, registering the address with a unique pointer, and preserving
// any existing ready state.
func mapAddressByPort(addr *v1.EndpointAddress, port v1.EndpointPort, ready bool, allAddrs map[addressKey]*v1.EndpointAddress, portToAddrReadyMap map[v1.EndpointPort]addressSet) *v1.EndpointAddress {
// use addressKey to distinguish between two endpoints that are identical addresses
// but may have come from different hosts, for attribution. For instance, Mesos
// assigns pods the node IP, but the pods are distinct.
key := addressKey{ip: addr.IP}
if addr.TargetRef != nil {
key.uid = addr.TargetRef.UID
}
// Accumulate the address. The full EndpointAddress structure is preserved for use when
// we rebuild the subsets so that the final TargetRef has all of the necessary data.
existingAddress := allAddrs[key]
if existingAddress == nil {
// Make a copy so we don't write to the
// input args of this function.
existingAddress = &v1.EndpointAddress{}
*existingAddress = *addr
allAddrs[key] = existingAddress
}
// Remember that this port maps to this address.
if _, found := portToAddrReadyMap[port]; !found {
portToAddrReadyMap[port] = addressSet{}
}
// if we have not yet recorded this port for this address, or if the previous
// state was ready, write the current ready state. not ready always trumps
// ready.
if wasReady, found := portToAddrReadyMap[port][existingAddress]; !found || wasReady {
portToAddrReadyMap[port][existingAddress] = ready
}
return existingAddress
}
type addressSet map[*v1.EndpointAddress]bool
type addrReady struct {
addr *v1.EndpointAddress
ready bool
}
func hashAddresses(addrs addressSet) string {
// Flatten the list of addresses into a string so it can be used as a
// map key. Unfortunately, DeepHashObject is implemented in terms of
// spew, and spew does not handle non-primitive map keys well. So
// first we collapse it into a slice, sort the slice, then hash that.
slice := make([]addrReady, 0, len(addrs))
for k, ready := range addrs {
slice = append(slice, addrReady{k, ready})
}
sort.Sort(addrsReady(slice))
hasher := md5.New()
hashutil.DeepHashObject(hasher, slice)
return hex.EncodeToString(hasher.Sum(nil)[0:])
}
func lessAddrReady(a, b addrReady) bool {
// ready is not significant to hashing since we can't have duplicate addresses
return LessEndpointAddress(a.addr, b.addr)
}
type addrsReady []addrReady
func (sl addrsReady) Len() int { return len(sl) }
func (sl addrsReady) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
func (sl addrsReady) Less(i, j int) bool {
return lessAddrReady(sl[i], sl[j])
}
func LessEndpointAddress(a, b *v1.EndpointAddress) bool {
ipComparison := bytes.Compare([]byte(a.IP), []byte(b.IP))
if ipComparison != 0 {
return ipComparison < 0
}
if b.TargetRef == nil {
return false
}
if a.TargetRef == nil {
return true
}
return a.TargetRef.UID < b.TargetRef.UID
}
type addrPtrsByIpAndUID []*v1.EndpointAddress
func (sl addrPtrsByIpAndUID) Len() int { return len(sl) }
func (sl addrPtrsByIpAndUID) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
func (sl addrPtrsByIpAndUID) Less(i, j int) bool {
return LessEndpointAddress(sl[i], sl[j])
}
// SortSubsets sorts an array of EndpointSubset objects in place. For ease of
// use it returns the input slice.
func SortSubsets(subsets []v1.EndpointSubset) []v1.EndpointSubset {
for i := range subsets {
ss := &subsets[i]
sort.Sort(addrsByIpAndUID(ss.Addresses))
sort.Sort(addrsByIpAndUID(ss.NotReadyAddresses))
sort.Sort(portsByHash(ss.Ports))
}
sort.Sort(subsetsByHash(subsets))
return subsets
}
func hashObject(hasher hash.Hash, obj interface{}) []byte {
hashutil.DeepHashObject(hasher, obj)
return hasher.Sum(nil)
}
type subsetsByHash []v1.EndpointSubset
func (sl subsetsByHash) Len() int { return len(sl) }
func (sl subsetsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
func (sl subsetsByHash) Less(i, j int) bool {
hasher := md5.New()
h1 := hashObject(hasher, sl[i])
h2 := hashObject(hasher, sl[j])
return bytes.Compare(h1, h2) < 0
}
type addrsByIpAndUID []v1.EndpointAddress
func (sl addrsByIpAndUID) Len() int { return len(sl) }
func (sl addrsByIpAndUID) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
func (sl addrsByIpAndUID) Less(i, j int) bool {
return LessEndpointAddress(&sl[i], &sl[j])
}
type portsByHash []v1.EndpointPort
func (sl portsByHash) Len() int { return len(sl) }
func (sl portsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
func (sl portsByHash) Less(i, j int) bool {
hasher := md5.New()
h1 := hashObject(hasher, sl[i])
h2 := hashObject(hasher, sl[j])
return bytes.Compare(h1, h2) < 0
}

View file

@ -0,0 +1,464 @@
/*
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 endpoints
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)
func podRef(uid string) *v1.ObjectReference {
ref := v1.ObjectReference{UID: types.UID(uid)}
return &ref
}
func TestPackSubsets(t *testing.T) {
// The downside of table-driven tests is that some things have to live outside the table.
fooObjRef := v1.ObjectReference{Name: "foo"}
barObjRef := v1.ObjectReference{Name: "bar"}
testCases := []struct {
name string
given []v1.EndpointSubset
expect []v1.EndpointSubset
}{
{
name: "empty everything",
given: []v1.EndpointSubset{{Addresses: []v1.EndpointAddress{}, Ports: []v1.EndpointPort{}}},
expect: []v1.EndpointSubset{},
}, {
name: "empty addresses",
given: []v1.EndpointSubset{{Addresses: []v1.EndpointAddress{}, Ports: []v1.EndpointPort{{Port: 111}}}},
expect: []v1.EndpointSubset{},
}, {
name: "empty ports",
given: []v1.EndpointSubset{{Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}}, Ports: []v1.EndpointPort{}}},
expect: []v1.EndpointSubset{},
}, {
name: "empty ports",
given: []v1.EndpointSubset{{NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}}, Ports: []v1.EndpointPort{}}},
expect: []v1.EndpointSubset{},
}, {
name: "one set, one ip, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one ip, one port (IPv6)",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "beef::1:2:3:4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "beef::1:2:3:4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one notReady ip, one port",
given: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one ip, one UID, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one notReady ip, one UID, one port",
given: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one ip, empty UID, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one notReady ip, empty UID, one port",
given: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, two ips, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}, {IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}, {IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, two mixed ips, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
NotReadyAddresses: []v1.EndpointAddress{{IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
NotReadyAddresses: []v1.EndpointAddress{{IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, two duplicate ips, one port, notReady is covered by ready",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one ip, two ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}, {Port: 222}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}, {Port: 222}},
}},
}, {
name: "one set, dup ips, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}, {IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, dup ips, one port (IPv6)",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "beef::1"}, {IP: "beef::1"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "beef::1"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, dup ips with target-refs, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{
{IP: "1.2.3.4", TargetRef: &fooObjRef},
{IP: "1.2.3.4", TargetRef: &barObjRef},
},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: &fooObjRef}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, dup mixed ips with target-refs, one port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{
{IP: "1.2.3.4", TargetRef: &fooObjRef},
},
NotReadyAddresses: []v1.EndpointAddress{
{IP: "1.2.3.4", TargetRef: &barObjRef},
},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
// finding the same address twice is considered an error on input, only the first address+port
// reference is preserved
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: &fooObjRef}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "one set, one ip, dup ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}, {Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two sets, dup ip, dup port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two sets, dup mixed ip, dup port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two sets, dup ip, two ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 222}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}, {Port: 222}},
}},
}, {
name: "two sets, dup ip, dup uids, two ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 222}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}, {Port: 222}},
}},
}, {
name: "two sets, dup mixed ip, dup uids, two ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 222}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 222}},
}},
}, {
name: "two sets, two ips, dup port",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}, {IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two set, dup ip, two uids, dup ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-2")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{
{IP: "1.2.3.4", TargetRef: podRef("uid-1")},
{IP: "1.2.3.4", TargetRef: podRef("uid-2")},
},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two set, dup ip, with and without uid, dup ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-2")}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{
{IP: "1.2.3.4"},
{IP: "1.2.3.4", TargetRef: podRef("uid-2")},
},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two sets, two ips, two dup ip with uid, dup port, wrong order",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "5.6.7.8", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{
{IP: "1.2.3.4"},
{IP: "1.2.3.4", TargetRef: podRef("uid-1")},
{IP: "5.6.7.8"},
{IP: "5.6.7.8", TargetRef: podRef("uid-1")},
},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two sets, two mixed ips, two dup ip with uid, dup port, wrong order, ends up with split addresses",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "5.6.7.8", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4", TargetRef: podRef("uid-1")}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{
{IP: "5.6.7.8"},
},
NotReadyAddresses: []v1.EndpointAddress{
{IP: "1.2.3.4"},
{IP: "1.2.3.4", TargetRef: podRef("uid-1")},
{IP: "5.6.7.8", TargetRef: podRef("uid-1")},
},
Ports: []v1.EndpointPort{{Port: 111}},
}},
}, {
name: "two sets, two ips, two ports",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 222}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "5.6.7.8"}},
Ports: []v1.EndpointPort{{Port: 222}},
}},
}, {
name: "four sets, three ips, three ports, jumbled",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.5"}},
Ports: []v1.EndpointPort{{Port: 222}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.6"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.5"}},
Ports: []v1.EndpointPort{{Port: 333}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}, {IP: "1.2.3.6"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.5"}},
Ports: []v1.EndpointPort{{Port: 222}, {Port: 333}},
}},
}, {
name: "four sets, three mixed ips, three ports, jumbled",
given: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.5"}},
Ports: []v1.EndpointPort{{Port: 222}},
}, {
Addresses: []v1.EndpointAddress{{IP: "1.2.3.6"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.5"}},
Ports: []v1.EndpointPort{{Port: 333}},
}},
expect: []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}, {IP: "1.2.3.6"}},
Ports: []v1.EndpointPort{{Port: 111}},
}, {
NotReadyAddresses: []v1.EndpointAddress{{IP: "1.2.3.5"}},
Ports: []v1.EndpointPort{{Port: 222}, {Port: 333}},
}},
},
}
for _, tc := range testCases {
result := RepackSubsets(tc.given)
if !reflect.DeepEqual(result, SortSubsets(tc.expect)) {
t.Errorf("case %q: expected %s, got %s", tc.name, spew.Sprintf("%#v", SortSubsets(tc.expect)), spew.Sprintf("%#v", result))
}
}
}

View file

@ -0,0 +1,590 @@
/*
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 helper
import (
"fmt"
"reflect"
"testing"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
func TestIsOpaqueIntResourceName(t *testing.T) { // resourceName input with the correct OpaqueIntResourceName prefix ("pod.alpha.kubernetes.io/opaque-int-resource-") should pass
testCases := []struct {
resourceName v1.ResourceName
expectVal bool
}{
{
resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo",
expectVal: true, // resourceName should pass because the resourceName has the correct prefix.
},
{
resourceName: "foo",
expectVal: false, // resourceName should fail because the resourceName has the wrong prefix.
},
{
resourceName: "",
expectVal: false, // resourceName should fail, empty resourceName.
},
}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) {
t.Parallel()
v := IsOpaqueIntResourceName(tc.resourceName)
if v != tc.expectVal {
t.Errorf("Got %v but expected %v", v, tc.expectVal)
}
})
}
}
func TestOpaqueIntResourceName(t *testing.T) { // each output should have the correct appended prefix ("pod.alpha.kubernetes.io/opaque-int-resource-") for opaque counted resources.
testCases := []struct {
name string
expectVal v1.ResourceName
}{
{
name: "foo",
expectVal: "pod.alpha.kubernetes.io/opaque-int-resource-foo", // append prefix to input string foo
},
{
name: "",
expectVal: "pod.alpha.kubernetes.io/opaque-int-resource-", // append prefix to input empty string
},
}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("name input=%s, expected value=%s", tc.name, tc.expectVal), func(t *testing.T) {
t.Parallel()
v := OpaqueIntResourceName(tc.name)
if v != tc.expectVal {
t.Errorf("Got %v but expected %v", v, tc.expectVal)
}
})
}
}
func TestAddToNodeAddresses(t *testing.T) {
testCases := []struct {
existing []v1.NodeAddress
toAdd []v1.NodeAddress
expected []v1.NodeAddress
}{
{
existing: []v1.NodeAddress{},
toAdd: []v1.NodeAddress{},
expected: []v1.NodeAddress{},
},
{
existing: []v1.NodeAddress{},
toAdd: []v1.NodeAddress{
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
{Type: v1.NodeHostName, Address: "localhost"},
},
expected: []v1.NodeAddress{
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
{Type: v1.NodeHostName, Address: "localhost"},
},
},
{
existing: []v1.NodeAddress{},
toAdd: []v1.NodeAddress{
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
},
expected: []v1.NodeAddress{
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
},
},
{
existing: []v1.NodeAddress{
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
{Type: v1.NodeInternalIP, Address: "10.1.1.1"},
},
toAdd: []v1.NodeAddress{
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
{Type: v1.NodeHostName, Address: "localhost"},
},
expected: []v1.NodeAddress{
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
{Type: v1.NodeInternalIP, Address: "10.1.1.1"},
{Type: v1.NodeHostName, Address: "localhost"},
},
},
}
for i, tc := range testCases {
AddToNodeAddresses(&tc.existing, tc.toAdd...)
if !apiequality.Semantic.DeepEqual(tc.expected, tc.existing) {
t.Errorf("case[%d], expected: %v, got: %v", i, tc.expected, tc.existing)
}
}
}
func TestGetAccessModesFromString(t *testing.T) {
modes := GetAccessModesFromString("ROX")
if !containsAccessMode(modes, v1.ReadOnlyMany) {
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
}
modes = GetAccessModesFromString("ROX,RWX")
if !containsAccessMode(modes, v1.ReadOnlyMany) {
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
}
if !containsAccessMode(modes, v1.ReadWriteMany) {
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
}
modes = GetAccessModesFromString("RWO,ROX,RWX")
if !containsAccessMode(modes, v1.ReadOnlyMany) {
t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
}
if !containsAccessMode(modes, v1.ReadWriteMany) {
t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
}
}
func TestRemoveDuplicateAccessModes(t *testing.T) {
modes := []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadOnlyMany, v1.ReadOnlyMany,
}
modes = removeDuplicateAccessModes(modes)
if len(modes) != 2 {
t.Errorf("Expected 2 distinct modes in set but found %v", len(modes))
}
}
func TestNodeSelectorRequirementsAsSelector(t *testing.T) {
matchExpressions := []v1.NodeSelectorRequirement{{
Key: "foo",
Operator: v1.NodeSelectorOpIn,
Values: []string{"bar", "baz"},
}}
mustParse := func(s string) labels.Selector {
out, e := labels.Parse(s)
if e != nil {
panic(e)
}
return out
}
tc := []struct {
in []v1.NodeSelectorRequirement
out labels.Selector
expectErr bool
}{
{in: nil, out: labels.Nothing()},
{in: []v1.NodeSelectorRequirement{}, out: labels.Nothing()},
{
in: matchExpressions,
out: mustParse("foo in (baz,bar)"),
},
{
in: []v1.NodeSelectorRequirement{{
Key: "foo",
Operator: v1.NodeSelectorOpExists,
Values: []string{"bar", "baz"},
}},
expectErr: true,
},
{
in: []v1.NodeSelectorRequirement{{
Key: "foo",
Operator: v1.NodeSelectorOpGt,
Values: []string{"1"},
}},
out: mustParse("foo>1"),
},
{
in: []v1.NodeSelectorRequirement{{
Key: "bar",
Operator: v1.NodeSelectorOpLt,
Values: []string{"7"},
}},
out: mustParse("bar<7"),
},
}
for i, tc := range tc {
out, err := NodeSelectorRequirementsAsSelector(tc.in)
if err == nil && tc.expectErr {
t.Errorf("[%v]expected error but got none.", i)
}
if err != nil && !tc.expectErr {
t.Errorf("[%v]did not expect error but got: %v", i, err)
}
if !reflect.DeepEqual(out, tc.out) {
t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out)
}
}
}
func TestTolerationsTolerateTaintsWithFilter(t *testing.T) {
testCases := []struct {
description string
tolerations []v1.Toleration
taints []v1.Taint
applyFilter taintsFilterFunc
expectTolerated bool
}{
{
description: "empty tolerations tolerate empty taints",
tolerations: []v1.Toleration{},
taints: []v1.Taint{},
applyFilter: func(t *v1.Taint) bool { return true },
expectTolerated: true,
},
{
description: "non-empty tolerations tolerate empty taints",
tolerations: []v1.Toleration{
{
Key: "foo",
Operator: "Exists",
Effect: v1.TaintEffectNoSchedule,
},
},
taints: []v1.Taint{},
applyFilter: func(t *v1.Taint) bool { return true },
expectTolerated: true,
},
{
description: "tolerations match all taints, expect tolerated",
tolerations: []v1.Toleration{
{
Key: "foo",
Operator: "Exists",
Effect: v1.TaintEffectNoSchedule,
},
},
taints: []v1.Taint{
{
Key: "foo",
Effect: v1.TaintEffectNoSchedule,
},
},
applyFilter: func(t *v1.Taint) bool { return true },
expectTolerated: true,
},
{
description: "tolerations don't match taints, but no taints apply to the filter, expect tolerated",
tolerations: []v1.Toleration{
{
Key: "foo",
Operator: "Exists",
Effect: v1.TaintEffectNoSchedule,
},
},
taints: []v1.Taint{
{
Key: "bar",
Effect: v1.TaintEffectNoSchedule,
},
},
applyFilter: func(t *v1.Taint) bool { return false },
expectTolerated: true,
},
{
description: "no filterFunc indicated, means all taints apply to the filter, tolerations don't match taints, expect untolerated",
tolerations: []v1.Toleration{
{
Key: "foo",
Operator: "Exists",
Effect: v1.TaintEffectNoSchedule,
},
},
taints: []v1.Taint{
{
Key: "bar",
Effect: v1.TaintEffectNoSchedule,
},
},
applyFilter: nil,
expectTolerated: false,
},
{
description: "tolerations match taints, expect tolerated",
tolerations: []v1.Toleration{
{
Key: "foo",
Operator: "Exists",
Effect: v1.TaintEffectNoExecute,
},
},
taints: []v1.Taint{
{
Key: "foo",
Effect: v1.TaintEffectNoExecute,
},
{
Key: "bar",
Effect: v1.TaintEffectNoSchedule,
},
},
applyFilter: func(t *v1.Taint) bool { return t.Effect == v1.TaintEffectNoExecute },
expectTolerated: true,
},
}
for _, tc := range testCases {
if tc.expectTolerated != TolerationsTolerateTaintsWithFilter(tc.tolerations, tc.taints, tc.applyFilter) {
filteredTaints := []v1.Taint{}
for _, taint := range tc.taints {
if tc.applyFilter != nil && !tc.applyFilter(&taint) {
continue
}
filteredTaints = append(filteredTaints, taint)
}
t.Errorf("[%s] expect tolerations %+v tolerate filtered taints %+v in taints %+v", tc.description, tc.tolerations, filteredTaints, tc.taints)
}
}
}
func TestGetAvoidPodsFromNode(t *testing.T) {
controllerFlag := true
testCases := []struct {
node *v1.Node
expectValue v1.AvoidPods
expectErr bool
}{
{
node: &v1.Node{},
expectValue: v1.AvoidPods{},
expectErr: false,
},
{
node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
v1.PreferAvoidPodsAnnotationKey: `
{
"preferAvoidPods": [
{
"podSignature": {
"podController": {
"apiVersion": "v1",
"kind": "ReplicationController",
"name": "foo",
"uid": "abcdef123456",
"controller": true
}
},
"reason": "some reason",
"message": "some message"
}
]
}`,
},
},
},
expectValue: v1.AvoidPods{
PreferAvoidPods: []v1.PreferAvoidPodsEntry{
{
PodSignature: v1.PodSignature{
PodController: &metav1.OwnerReference{
APIVersion: "v1",
Kind: "ReplicationController",
Name: "foo",
UID: "abcdef123456",
Controller: &controllerFlag,
},
},
Reason: "some reason",
Message: "some message",
},
},
},
expectErr: false,
},
{
node: &v1.Node{
// Missing end symbol of "podController" and "podSignature"
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
v1.PreferAvoidPodsAnnotationKey: `
{
"preferAvoidPods": [
{
"podSignature": {
"podController": {
"kind": "ReplicationController",
"apiVersion": "v1"
"reason": "some reason",
"message": "some message"
}
]
}`,
},
},
},
expectValue: v1.AvoidPods{},
expectErr: true,
},
}
for i, tc := range testCases {
v, err := GetAvoidPodsFromNodeAnnotations(tc.node.Annotations)
if err == nil && tc.expectErr {
t.Errorf("[%v]expected error but got none.", i)
}
if err != nil && !tc.expectErr {
t.Errorf("[%v]did not expect error but got: %v", i, err)
}
if !reflect.DeepEqual(tc.expectValue, v) {
t.Errorf("[%v]expect value %v but got %v with %v", i, tc.expectValue, v, v.PreferAvoidPods[0].PodSignature.PodController.Controller)
}
}
}
func TestSysctlsFromPodAnnotation(t *testing.T) {
type Test struct {
annotation string
expectValue []v1.Sysctl
expectErr bool
}
for i, test := range []Test{
{
annotation: "",
expectValue: nil,
},
{
annotation: "foo.bar",
expectErr: true,
},
{
annotation: "=123",
expectErr: true,
},
{
annotation: "foo.bar=",
expectValue: []v1.Sysctl{{Name: "foo.bar", Value: ""}},
},
{
annotation: "foo.bar=42",
expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}},
},
{
annotation: "foo.bar=42,",
expectErr: true,
},
{
annotation: "foo.bar=42,abc.def=1",
expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}, {Name: "abc.def", Value: "1"}},
},
} {
sysctls, err := SysctlsFromPodAnnotation(test.annotation)
if test.expectErr && err == nil {
t.Errorf("[%v]expected error but got none", i)
} else if !test.expectErr && err != nil {
t.Errorf("[%v]did not expect error but got: %v", i, err)
} else if !reflect.DeepEqual(sysctls, test.expectValue) {
t.Errorf("[%v]expect value %v but got %v", i, test.expectValue, sysctls)
}
}
}
// TODO: remove when alpha support for topology constraints is removed
func TestGetNodeAffinityFromAnnotations(t *testing.T) {
testCases := []struct {
annotations map[string]string
expectErr bool
}{
{
annotations: nil,
expectErr: false,
},
{
annotations: map[string]string{},
expectErr: false,
},
{
annotations: map[string]string{
v1.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}`,
},
expectErr: false,
},
{
annotations: map[string]string{
v1.AlphaStorageNodeAffinityAnnotation: `[{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}]`,
},
expectErr: true,
},
{
annotations: map[string]string{
v1.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms":
"matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
}
}`,
},
expectErr: true,
},
}
for i, tc := range testCases {
_, err := GetStorageNodeAffinityFromAnnotation(tc.annotations)
if err == nil && tc.expectErr {
t.Errorf("[%v]expected error but got none.", i)
}
if err != nil && !tc.expectErr {
t.Errorf("[%v]did not expect error but got: %v", i, err)
}
}
}

45
vendor/k8s.io/kubernetes/pkg/api/v1/helper/qos/BUILD generated vendored Normal file
View file

@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["qos_test.go"],
library = ":go_default_library",
deps = [
"//pkg/api:go_default_library",
"//pkg/api/helper/qos:go_default_library",
"//pkg/api/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["qos.go"],
deps = [
"//pkg/api/v1/helper:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

98
vendor/k8s.io/kubernetes/pkg/api/v1/helper/qos/qos.go generated vendored Normal file
View file

@ -0,0 +1,98 @@
/*
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 qos
import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
)
// QOSList is a set of (resource name, QoS class) pairs.
type QOSList map[v1.ResourceName]v1.PodQOSClass
func isSupportedQoSComputeResource(name v1.ResourceName) bool {
supportedQoSComputeResources := sets.NewString(string(v1.ResourceCPU), string(v1.ResourceMemory))
return supportedQoSComputeResources.Has(string(name)) || v1helper.IsHugePageResourceName(name)
}
// GetPodQOS returns the QoS class of a pod.
// A pod is besteffort if none of its containers have specified any requests or limits.
// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal.
// A pod is burstable if limits and requests do not match across all containers.
func GetPodQOS(pod *v1.Pod) v1.PodQOSClass {
requests := v1.ResourceList{}
limits := v1.ResourceList{}
zeroQuantity := resource.MustParse("0")
isGuaranteed := true
for _, container := range pod.Spec.Containers {
// process requests
for name, quantity := range container.Resources.Requests {
if !isSupportedQoSComputeResource(name) {
continue
}
if quantity.Cmp(zeroQuantity) == 1 {
delta := quantity.Copy()
if _, exists := requests[name]; !exists {
requests[name] = *delta
} else {
delta.Add(requests[name])
requests[name] = *delta
}
}
}
// process limits
qosLimitsFound := sets.NewString()
for name, quantity := range container.Resources.Limits {
if !isSupportedQoSComputeResource(name) {
continue
}
if quantity.Cmp(zeroQuantity) == 1 {
qosLimitsFound.Insert(string(name))
delta := quantity.Copy()
if _, exists := limits[name]; !exists {
limits[name] = *delta
} else {
delta.Add(limits[name])
limits[name] = *delta
}
}
}
if !qosLimitsFound.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) {
isGuaranteed = false
}
}
if len(requests) == 0 && len(limits) == 0 {
return v1.PodQOSBestEffort
}
// Check is requests match limits for all resources.
if isGuaranteed {
for name, req := range requests {
if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 {
isGuaranteed = false
break
}
}
}
if isGuaranteed &&
len(requests) == len(limits) {
return v1.PodQOSGuaranteed
}
return v1.PodQOSBurstable
}

View file

@ -0,0 +1,194 @@
/*
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 qos
import (
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/helper/qos"
k8sv1 "k8s.io/kubernetes/pkg/api/v1"
)
func TestGetPodQOS(t *testing.T) {
testCases := []struct {
pod *v1.Pod
expected v1.PodQOSClass
}{
{
pod: newPod("guaranteed", []v1.Container{
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
}),
expected: v1.PodQOSGuaranteed,
},
{
pod: newPod("guaranteed-with-gpu", []v1.Container{
newContainer("guaranteed", getResourceList("100m", "100Mi"), addResource("nvidia-gpu", "2", getResourceList("100m", "100Mi"))),
}),
expected: v1.PodQOSGuaranteed,
},
{
pod: newPod("guaranteed-guaranteed", []v1.Container{
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
}),
expected: v1.PodQOSGuaranteed,
},
{
pod: newPod("guaranteed-guaranteed-with-gpu", []v1.Container{
newContainer("guaranteed", getResourceList("100m", "100Mi"), addResource("nvidia-gpu", "2", getResourceList("100m", "100Mi"))),
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
}),
expected: v1.PodQOSGuaranteed,
},
{
pod: newPod("best-effort-best-effort", []v1.Container{
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
}),
expected: v1.PodQOSBestEffort,
},
{
pod: newPod("best-effort-best-effort-with-gpu", []v1.Container{
newContainer("best-effort", getResourceList("", ""), addResource("nvidia-gpu", "2", getResourceList("", ""))),
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
}),
expected: v1.PodQOSBestEffort,
},
{
pod: newPod("best-effort-with-gpu", []v1.Container{
newContainer("best-effort", getResourceList("", ""), addResource("nvidia-gpu", "2", getResourceList("", ""))),
}),
expected: v1.PodQOSBestEffort,
},
{
pod: newPod("best-effort-burstable", []v1.Container{
newContainer("best-effort", getResourceList("", ""), addResource("nvidia-gpu", "2", getResourceList("", ""))),
newContainer("burstable", getResourceList("1", ""), getResourceList("2", "")),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("best-effort-guaranteed", []v1.Container{
newContainer("best-effort", getResourceList("", ""), addResource("nvidia-gpu", "2", getResourceList("", ""))),
newContainer("guaranteed", getResourceList("10m", "100Mi"), getResourceList("10m", "100Mi")),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-cpu-guaranteed-memory", []v1.Container{
newContainer("burstable", getResourceList("", "100Mi"), getResourceList("", "100Mi")),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-no-limits", []v1.Container{
newContainer("burstable", getResourceList("100m", "100Mi"), getResourceList("", "")),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-guaranteed", []v1.Container{
newContainer("burstable", getResourceList("1", "100Mi"), getResourceList("2", "100Mi")),
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-unbounded-but-requests-match-limits", []v1.Container{
newContainer("burstable", getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")),
newContainer("burstable-unbounded", getResourceList("100m", "100Mi"), getResourceList("", "")),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-1", []v1.Container{
newContainer("burstable", getResourceList("10m", "100Mi"), getResourceList("100m", "200Mi")),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-2", []v1.Container{
newContainer("burstable", getResourceList("0", "0"), addResource("nvidia-gpu", "2", getResourceList("100m", "200Mi"))),
}),
expected: v1.PodQOSBurstable,
},
{
pod: newPod("burstable-hugepages", []v1.Container{
newContainer("burstable", addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0")), addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0"))),
}),
expected: v1.PodQOSBurstable,
},
}
for id, testCase := range testCases {
if actual := GetPodQOS(testCase.pod); testCase.expected != actual {
t.Errorf("[%d]: invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual)
}
// Convert v1.Pod to api.Pod, and then check against `api.helper.GetPodQOS`.
pod := api.Pod{}
k8sv1.Convert_v1_Pod_To_api_Pod(testCase.pod, &pod, nil)
if actual := qos.GetPodQOS(&pod); api.PodQOSClass(testCase.expected) != actual {
t.Errorf("[%d]: conversion invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual)
}
}
}
func getResourceList(cpu, memory string) v1.ResourceList {
res := v1.ResourceList{}
if cpu != "" {
res[v1.ResourceCPU] = resource.MustParse(cpu)
}
if memory != "" {
res[v1.ResourceMemory] = resource.MustParse(memory)
}
return res
}
func addResource(rName, value string, rl v1.ResourceList) v1.ResourceList {
rl[v1.ResourceName(rName)] = resource.MustParse(value)
return rl
}
func getResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequirements {
res := v1.ResourceRequirements{}
res.Requests = requests
res.Limits = limits
return res
}
func newContainer(name string, requests v1.ResourceList, limits v1.ResourceList) v1.Container {
return v1.Container{
Name: name,
Resources: getResourceRequirements(requests, limits),
}
}
func newPod(name string, containers []v1.Container) *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1.PodSpec{
Containers: containers,
},
}
}

25
vendor/k8s.io/kubernetes/pkg/api/v1/node/BUILD generated vendored Normal file
View file

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

47
vendor/k8s.io/kubernetes/pkg/api/v1/node/util.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
/*
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.
*/
// TODO: merge with pkg/util/node
package node
import (
"k8s.io/api/core/v1"
)
// GetNodeCondition extracts the provided condition from the given status and returns that.
// Returns nil and -1 if the condition is not present, and the index of the located condition.
func GetNodeCondition(status *v1.NodeStatus, conditionType v1.NodeConditionType) (int, *v1.NodeCondition) {
if status == nil {
return -1, nil
}
for i := range status.Conditions {
if status.Conditions[i].Type == conditionType {
return i, &status.Conditions[i]
}
}
return -1, nil
}
// IsNodeReady returns true if a node is ready; false otherwise.
func IsNodeReady(node *v1.Node) bool {
for _, c := range node.Status.Conditions {
if c.Type == v1.NodeReady {
return c.Status == v1.ConditionTrue
}
}
return false
}

406
vendor/k8s.io/kubernetes/pkg/api/v1/pod/util_test.go generated vendored Normal file
View file

@ -0,0 +1,406 @@
/*
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 pod
import (
"reflect"
"strings"
"testing"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestFindPort(t *testing.T) {
testCases := []struct {
name string
containers []v1.Container
port intstr.IntOrString
expected int
pass bool
}{{
name: "valid int, no ports",
containers: []v1.Container{{}},
port: intstr.FromInt(93),
expected: 93,
pass: true,
}, {
name: "valid int, with ports",
containers: []v1.Container{{Ports: []v1.ContainerPort{{
Name: "",
ContainerPort: 11,
Protocol: "TCP",
}, {
Name: "p",
ContainerPort: 22,
Protocol: "TCP",
}}}},
port: intstr.FromInt(93),
expected: 93,
pass: true,
}, {
name: "valid str, no ports",
containers: []v1.Container{{}},
port: intstr.FromString("p"),
expected: 0,
pass: false,
}, {
name: "valid str, one ctr with ports",
containers: []v1.Container{{Ports: []v1.ContainerPort{{
Name: "",
ContainerPort: 11,
Protocol: "UDP",
}, {
Name: "p",
ContainerPort: 22,
Protocol: "TCP",
}, {
Name: "q",
ContainerPort: 33,
Protocol: "TCP",
}}}},
port: intstr.FromString("q"),
expected: 33,
pass: true,
}, {
name: "valid str, two ctr with ports",
containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
Name: "",
ContainerPort: 11,
Protocol: "UDP",
}, {
Name: "p",
ContainerPort: 22,
Protocol: "TCP",
}, {
Name: "q",
ContainerPort: 33,
Protocol: "TCP",
}}}},
port: intstr.FromString("q"),
expected: 33,
pass: true,
}, {
name: "valid str, two ctr with same port",
containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
Name: "",
ContainerPort: 11,
Protocol: "UDP",
}, {
Name: "p",
ContainerPort: 22,
Protocol: "TCP",
}, {
Name: "q",
ContainerPort: 22,
Protocol: "TCP",
}}}},
port: intstr.FromString("q"),
expected: 22,
pass: true,
}, {
name: "valid str, invalid protocol",
containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
Name: "a",
ContainerPort: 11,
Protocol: "snmp",
},
}}},
port: intstr.FromString("a"),
expected: 0,
pass: false,
}, {
name: "valid hostPort",
containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
Name: "a",
ContainerPort: 11,
HostPort: 81,
Protocol: "TCP",
},
}}},
port: intstr.FromString("a"),
expected: 11,
pass: true,
},
{
name: "invalid hostPort",
containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
Name: "a",
ContainerPort: 11,
HostPort: -1,
Protocol: "TCP",
},
}}},
port: intstr.FromString("a"),
expected: 11,
pass: true,
//this should fail but passes.
},
{
name: "invalid ContainerPort",
containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
Name: "a",
ContainerPort: -1,
Protocol: "TCP",
},
}}},
port: intstr.FromString("a"),
expected: -1,
pass: true,
//this should fail but passes
},
{
name: "HostIP Address",
containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
Name: "a",
ContainerPort: 11,
HostIP: "192.168.1.1",
Protocol: "TCP",
},
}}},
port: intstr.FromString("a"),
expected: 11,
pass: true,
},
}
for _, tc := range testCases {
port, err := FindPort(&v1.Pod{Spec: v1.PodSpec{Containers: tc.containers}},
&v1.ServicePort{Protocol: "TCP", TargetPort: tc.port})
if err != nil && tc.pass {
t.Errorf("unexpected error for %s: %v", tc.name, err)
}
if err == nil && !tc.pass {
t.Errorf("unexpected non-error for %s: %d", tc.name, port)
}
if port != tc.expected {
t.Errorf("wrong result for %s: expected %d, got %d", tc.name, tc.expected, port)
}
}
}
func TestPodSecrets(t *testing.T) {
// Stub containing all possible secret references in a pod.
// The names of the referenced secrets match struct paths detected by reflection.
pod := &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{{
EnvFrom: []v1.EnvFromSource{{
SecretRef: &v1.SecretEnvSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "Spec.Containers[*].EnvFrom[*].SecretRef"}}}},
Env: []v1.EnvVar{{
ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}},
ImagePullSecrets: []v1.LocalObjectReference{{
Name: "Spec.ImagePullSecrets"}},
InitContainers: []v1.Container{{
EnvFrom: []v1.EnvFromSource{{
SecretRef: &v1.SecretEnvSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "Spec.InitContainers[*].EnvFrom[*].SecretRef"}}}},
Env: []v1.EnvVar{{
ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}},
Volumes: []v1.Volume{{
VolumeSource: v1.VolumeSource{
AzureFile: &v1.AzureFileVolumeSource{
SecretName: "Spec.Volumes[*].VolumeSource.AzureFile.SecretName"}}}, {
VolumeSource: v1.VolumeSource{
CephFS: &v1.CephFSVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.CephFS.SecretRef"}}}}, {
VolumeSource: v1.VolumeSource{
FlexVolume: &v1.FlexVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef"}}}}, {
VolumeSource: v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{
Sources: []v1.VolumeProjection{{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret"}}}}}}}, {
VolumeSource: v1.VolumeSource{
RBD: &v1.RBDVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.RBD.SecretRef"}}}}, {
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: "Spec.Volumes[*].VolumeSource.Secret.SecretName"}}}, {
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: "Spec.Volumes[*].VolumeSource.Secret"}}}, {
VolumeSource: v1.VolumeSource{
ScaleIO: &v1.ScaleIOVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}, {
VolumeSource: v1.VolumeSource{
ISCSI: &v1.ISCSIVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}, {
VolumeSource: v1.VolumeSource{
StorageOS: &v1.StorageOSVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef"}}}}},
},
}
extractedNames := sets.NewString()
VisitPodSecretNames(pod, func(name string) bool {
extractedNames.Insert(name)
return true
})
// excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects
excludedSecretPaths := sets.NewString(
"Spec.Volumes[*].VolumeSource.CephFS.SecretFile",
)
// expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects.
// every path here should be represented as an example in the Pod stub above, with the secret name set to the path.
expectedSecretPaths := sets.NewString(
"Spec.Containers[*].EnvFrom[*].SecretRef",
"Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef",
"Spec.ImagePullSecrets",
"Spec.InitContainers[*].EnvFrom[*].SecretRef",
"Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef",
"Spec.Volumes[*].VolumeSource.AzureFile.SecretName",
"Spec.Volumes[*].VolumeSource.CephFS.SecretRef",
"Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef",
"Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret",
"Spec.Volumes[*].VolumeSource.RBD.SecretRef",
"Spec.Volumes[*].VolumeSource.Secret",
"Spec.Volumes[*].VolumeSource.Secret.SecretName",
"Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef",
"Spec.Volumes[*].VolumeSource.ISCSI.SecretRef",
"Spec.Volumes[*].VolumeSource.StorageOS.SecretRef",
)
secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&v1.Pod{}))
secretPaths = secretPaths.Difference(excludedSecretPaths)
if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 {
t.Logf("Missing expected secret paths:\n%s", strings.Join(missingPaths.List(), "\n"))
t.Error("Missing expected secret paths. Verify VisitPodSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths")
}
if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 {
t.Logf("Extra secret paths:\n%s", strings.Join(extraPaths.List(), "\n"))
t.Error("Extra fields with 'secret' in the name found. Verify VisitPodSecretNames() is including these fields if appropriate, then correct expectedSecretPaths")
}
if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 {
t.Logf("Missing expected secret names:\n%s", strings.Join(missingNames.List(), "\n"))
t.Error("Missing expected secret names. Verify the pod stub above includes these references, then verify VisitPodSecretNames() is correctly finding the missing names")
}
if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 {
t.Logf("Extra secret names:\n%s", strings.Join(extraNames.List(), "\n"))
t.Error("Extra secret names extracted. Verify VisitPodSecretNames() is correctly extracting secret names")
}
}
// collectSecretPaths traverses the object, computing all the struct paths that lead to fields with "secret" in the name.
func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.Type) sets.String {
secretPaths := sets.NewString()
if tp.Kind() == reflect.Ptr {
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
return secretPaths
}
if strings.Contains(strings.ToLower(name), "secret") {
secretPaths.Insert(path.String())
}
switch tp.Kind() {
case reflect.Ptr:
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
case reflect.Struct:
for i := 0; i < tp.NumField(); i++ {
field := tp.Field(i)
secretPaths.Insert(collectSecretPaths(t, path.Child(field.Name), field.Name, field.Type).List()...)
}
case reflect.Interface:
t.Errorf("cannot find secret fields in interface{} field %s", path.String())
case reflect.Map:
secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...)
case reflect.Slice:
secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...)
default:
// all primitive types
}
return secretPaths
}
func newPod(now metav1.Time, ready bool, beforeSec int) *v1.Pod {
conditionStatus := v1.ConditionFalse
if ready {
conditionStatus = v1.ConditionTrue
}
return &v1.Pod{
Status: v1.PodStatus{
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
LastTransitionTime: metav1.NewTime(now.Time.Add(-1 * time.Duration(beforeSec) * time.Second)),
Status: conditionStatus,
},
},
},
}
}
func TestIsPodAvailable(t *testing.T) {
now := metav1.Now()
tests := []struct {
pod *v1.Pod
minReadySeconds int32
expected bool
}{
{
pod: newPod(now, false, 0),
minReadySeconds: 0,
expected: false,
},
{
pod: newPod(now, true, 0),
minReadySeconds: 1,
expected: false,
},
{
pod: newPod(now, true, 0),
minReadySeconds: 0,
expected: true,
},
{
pod: newPod(now, true, 51),
minReadySeconds: 50,
expected: true,
},
}
for i, test := range tests {
isAvailable := IsPodAvailable(test.pod, test.minReadySeconds, now)
if isAvailable != test.expected {
t.Errorf("[tc #%d] expected available pod: %t, got: %t", i, test.expected, isAvailable)
}
}
}

40
vendor/k8s.io/kubernetes/pkg/api/v1/resource/BUILD generated vendored Normal file
View file

@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["helpers_test.go"],
library = ":go_default_library",
deps = [
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["helpers.go"],
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

206
vendor/k8s.io/kubernetes/pkg/api/v1/resource/helpers.go generated vendored Normal file
View file

@ -0,0 +1,206 @@
/*
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 resource
import (
"fmt"
"math"
"strconv"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
// containers of the pod.
func PodRequestsAndLimits(pod *v1.Pod) (reqs map[v1.ResourceName]resource.Quantity, limits map[v1.ResourceName]resource.Quantity) {
reqs, limits = map[v1.ResourceName]resource.Quantity{}, map[v1.ResourceName]resource.Quantity{}
for _, container := range pod.Spec.Containers {
for name, quantity := range container.Resources.Requests {
if value, ok := reqs[name]; !ok {
reqs[name] = *quantity.Copy()
} else {
value.Add(quantity)
reqs[name] = value
}
}
for name, quantity := range container.Resources.Limits {
if value, ok := limits[name]; !ok {
limits[name] = *quantity.Copy()
} else {
value.Add(quantity)
limits[name] = value
}
}
}
// init containers define the minimum of any resource
for _, container := range pod.Spec.InitContainers {
for name, quantity := range container.Resources.Requests {
value, ok := reqs[name]
if !ok {
reqs[name] = *quantity.Copy()
continue
}
if quantity.Cmp(value) > 0 {
reqs[name] = *quantity.Copy()
}
}
for name, quantity := range container.Resources.Limits {
value, ok := limits[name]
if !ok {
limits[name] = *quantity.Copy()
continue
}
if quantity.Cmp(value) > 0 {
limits[name] = *quantity.Copy()
}
}
}
return
}
// finds and returns the request for a specific resource.
func GetResourceRequest(pod *v1.Pod, resource v1.ResourceName) int64 {
if resource == v1.ResourcePods {
return 1
}
totalResources := int64(0)
for _, container := range pod.Spec.Containers {
if rQuantity, ok := container.Resources.Requests[resource]; ok {
if resource == v1.ResourceCPU {
totalResources += rQuantity.MilliValue()
} else {
totalResources += rQuantity.Value()
}
}
}
// take max_resource(sum_pod, any_init_container)
for _, container := range pod.Spec.InitContainers {
if rQuantity, ok := container.Resources.Requests[resource]; ok {
if resource == v1.ResourceCPU && rQuantity.MilliValue() > totalResources {
totalResources = rQuantity.MilliValue()
} else if rQuantity.Value() > totalResources {
totalResources = rQuantity.Value()
}
}
}
return totalResources
}
// ExtractResourceValueByContainerName extracts the value of a resource
// by providing container name
func ExtractResourceValueByContainerName(fs *v1.ResourceFieldSelector, pod *v1.Pod, containerName string) (string, error) {
container, err := findContainerInPod(pod, containerName)
if err != nil {
return "", err
}
return ExtractContainerResourceValue(fs, container)
}
type deepCopier interface {
DeepCopy(interface{}) (interface{}, error)
}
// ExtractResourceValueByContainerNameAndNodeAllocatable extracts the value of a resource
// by providing container name and node allocatable
func ExtractResourceValueByContainerNameAndNodeAllocatable(copier deepCopier, fs *v1.ResourceFieldSelector, pod *v1.Pod, containerName string, nodeAllocatable v1.ResourceList) (string, error) {
realContainer, err := findContainerInPod(pod, containerName)
if err != nil {
return "", err
}
container := realContainer.DeepCopy()
MergeContainerResourceLimits(container, nodeAllocatable)
return ExtractContainerResourceValue(fs, container)
}
// ExtractContainerResourceValue extracts the value of a resource
// in an already known container
func ExtractContainerResourceValue(fs *v1.ResourceFieldSelector, container *v1.Container) (string, error) {
divisor := resource.Quantity{}
if divisor.Cmp(fs.Divisor) == 0 {
divisor = resource.MustParse("1")
} else {
divisor = fs.Divisor
}
switch fs.Resource {
case "limits.cpu":
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
case "limits.memory":
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
case "limits.ephemeral-storage":
return convertResourceEphemeralStorageToString(container.Resources.Limits.StorageEphemeral(), divisor)
case "requests.cpu":
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
case "requests.memory":
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
case "requests.ephemeral-storage":
return convertResourceEphemeralStorageToString(container.Resources.Requests.StorageEphemeral(), divisor)
}
return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
}
// convertResourceCPUToString converts cpu value to the format of divisor and returns
// ceiling of the value.
func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
return strconv.FormatInt(c, 10), nil
}
// convertResourceMemoryToString converts memory value to the format of divisor and returns
// ceiling of the value.
func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
return strconv.FormatInt(m, 10), nil
}
// convertResourceEphemeralStorageToString converts ephemeral storage value to the format of divisor and returns
// ceiling of the value.
func convertResourceEphemeralStorageToString(ephemeralStorage *resource.Quantity, divisor resource.Quantity) (string, error) {
m := int64(math.Ceil(float64(ephemeralStorage.Value()) / float64(divisor.Value())))
return strconv.FormatInt(m, 10), nil
}
// findContainerInPod finds a container by its name in the provided pod
func findContainerInPod(pod *v1.Pod, containerName string) (*v1.Container, error) {
for _, container := range pod.Spec.Containers {
if container.Name == containerName {
return &container, nil
}
}
return nil, fmt.Errorf("container %s not found", containerName)
}
// MergeContainerResourceLimits checks if a limit is applied for
// the container, and if not, it sets the limit to the passed resource list.
func MergeContainerResourceLimits(container *v1.Container,
allocatable v1.ResourceList) {
if container.Resources.Limits == nil {
container.Resources.Limits = make(v1.ResourceList)
}
for _, resource := range []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory} {
if quantity, exists := container.Resources.Limits[resource]; !exists || quantity.IsZero() {
if cap, exists := allocatable[resource]; exists {
container.Resources.Limits[resource] = *cap.Copy()
}
}
}
}

View file

@ -0,0 +1,180 @@
/*
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 resource
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
func TestResourceHelpers(t *testing.T) {
cpuLimit := resource.MustParse("10")
memoryLimit := resource.MustParse("10G")
resourceSpec := v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceCPU: cpuLimit,
v1.ResourceMemory: memoryLimit,
},
}
if res := resourceSpec.Limits.Cpu(); res.Cmp(cpuLimit) != 0 {
t.Errorf("expected cpulimit %v, got %v", cpuLimit, res)
}
if res := resourceSpec.Limits.Memory(); res.Cmp(memoryLimit) != 0 {
t.Errorf("expected memorylimit %v, got %v", memoryLimit, res)
}
resourceSpec = v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceMemory: memoryLimit,
},
}
if res := resourceSpec.Limits.Cpu(); res.Value() != 0 {
t.Errorf("expected cpulimit %v, got %v", 0, res)
}
if res := resourceSpec.Limits.Memory(); res.Cmp(memoryLimit) != 0 {
t.Errorf("expected memorylimit %v, got %v", memoryLimit, res)
}
}
func TestDefaultResourceHelpers(t *testing.T) {
resourceList := v1.ResourceList{}
if resourceList.Cpu().Format != resource.DecimalSI {
t.Errorf("expected %v, actual %v", resource.DecimalSI, resourceList.Cpu().Format)
}
if resourceList.Memory().Format != resource.BinarySI {
t.Errorf("expected %v, actual %v", resource.BinarySI, resourceList.Memory().Format)
}
}
func TestExtractResourceValue(t *testing.T) {
cases := []struct {
fs *v1.ResourceFieldSelector
pod *v1.Pod
cName string
expectedValue string
expectedError error
}{
{
fs: &v1.ResourceFieldSelector{
Resource: "limits.cpu",
},
cName: "foo",
pod: getPod("foo", "", "9", "", ""),
expectedValue: "9",
},
{
fs: &v1.ResourceFieldSelector{
Resource: "requests.cpu",
},
cName: "foo",
pod: getPod("foo", "", "", "", ""),
expectedValue: "0",
},
{
fs: &v1.ResourceFieldSelector{
Resource: "requests.cpu",
},
cName: "foo",
pod: getPod("foo", "8", "", "", ""),
expectedValue: "8",
},
{
fs: &v1.ResourceFieldSelector{
Resource: "requests.cpu",
},
cName: "foo",
pod: getPod("foo", "100m", "", "", ""),
expectedValue: "1",
},
{
fs: &v1.ResourceFieldSelector{
Resource: "requests.cpu",
Divisor: resource.MustParse("100m"),
},
cName: "foo",
pod: getPod("foo", "1200m", "", "", ""),
expectedValue: "12",
},
{
fs: &v1.ResourceFieldSelector{
Resource: "requests.memory",
},
cName: "foo",
pod: getPod("foo", "", "", "100Mi", ""),
expectedValue: "104857600",
},
{
fs: &v1.ResourceFieldSelector{
Resource: "requests.memory",
Divisor: resource.MustParse("1Mi"),
},
cName: "foo",
pod: getPod("foo", "", "", "100Mi", "1Gi"),
expectedValue: "100",
},
{
fs: &v1.ResourceFieldSelector{
Resource: "limits.memory",
},
cName: "foo",
pod: getPod("foo", "", "", "10Mi", "100Mi"),
expectedValue: "104857600",
},
}
as := assert.New(t)
for idx, tc := range cases {
actual, err := ExtractResourceValueByContainerName(tc.fs, tc.pod, tc.cName)
if tc.expectedError != nil {
as.Equal(tc.expectedError, err, "expected test case [%d] to fail with error %v; got %v", idx, tc.expectedError, err)
} else {
as.Nil(err, "expected test case [%d] to not return an error; got %v", idx, err)
as.Equal(tc.expectedValue, actual, "expected test case [%d] to return %q; got %q instead", idx, tc.expectedValue, actual)
}
}
}
func getPod(cname, cpuRequest, cpuLimit, memoryRequest, memoryLimit string) *v1.Pod {
resources := v1.ResourceRequirements{
Limits: make(v1.ResourceList),
Requests: make(v1.ResourceList),
}
if cpuLimit != "" {
resources.Limits[v1.ResourceCPU] = resource.MustParse(cpuLimit)
}
if memoryLimit != "" {
resources.Limits[v1.ResourceMemory] = resource.MustParse(memoryLimit)
}
if cpuRequest != "" {
resources.Requests[v1.ResourceCPU] = resource.MustParse(cpuRequest)
}
if memoryRequest != "" {
resources.Requests[v1.ResourceMemory] = resource.MustParse(memoryRequest)
}
return &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: cname,
Resources: resources,
},
},
},
}
}

View file

@ -0,0 +1,216 @@
/*
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 service
import (
"strings"
"testing"
"k8s.io/api/core/v1"
netsets "k8s.io/kubernetes/pkg/util/net/sets"
)
func TestGetLoadBalancerSourceRanges(t *testing.T) {
checkError := func(v string) {
annotations := make(map[string]string)
annotations[v1.AnnotationLoadBalancerSourceRangesKey] = v
svc := v1.Service{}
svc.Annotations = annotations
_, err := GetLoadBalancerSourceRanges(&svc)
if err == nil {
t.Errorf("Expected error parsing: %q", v)
}
svc = v1.Service{}
svc.Spec.LoadBalancerSourceRanges = strings.Split(v, ",")
_, err = GetLoadBalancerSourceRanges(&svc)
if err == nil {
t.Errorf("Expected error parsing: %q", v)
}
}
checkError("10.0.0.1/33")
checkError("foo.bar")
checkError("10.0.0.1/32,*")
checkError("10.0.0.1/32,")
checkError("10.0.0.1/32, ")
checkError("10.0.0.1")
checkOK := func(v string) netsets.IPNet {
annotations := make(map[string]string)
annotations[v1.AnnotationLoadBalancerSourceRangesKey] = v
svc := v1.Service{}
svc.Annotations = annotations
cidrs, err := GetLoadBalancerSourceRanges(&svc)
if err != nil {
t.Errorf("Unexpected error parsing: %q", v)
}
svc = v1.Service{}
svc.Spec.LoadBalancerSourceRanges = strings.Split(v, ",")
cidrs, err = GetLoadBalancerSourceRanges(&svc)
if err != nil {
t.Errorf("Unexpected error parsing: %q", v)
}
return cidrs
}
cidrs := checkOK("192.168.0.1/32")
if len(cidrs) != 1 {
t.Errorf("Expected exactly one CIDR: %v", cidrs.StringSlice())
}
cidrs = checkOK("192.168.0.1/32,192.168.0.1/32")
if len(cidrs) != 1 {
t.Errorf("Expected exactly one CIDR (after de-dup): %v", cidrs.StringSlice())
}
cidrs = checkOK("192.168.0.1/32,192.168.0.2/32")
if len(cidrs) != 2 {
t.Errorf("Expected two CIDRs: %v", cidrs.StringSlice())
}
cidrs = checkOK(" 192.168.0.1/32 , 192.168.0.2/32 ")
if len(cidrs) != 2 {
t.Errorf("Expected two CIDRs: %v", cidrs.StringSlice())
}
// check LoadBalancerSourceRanges not specified
svc := v1.Service{}
cidrs, err := GetLoadBalancerSourceRanges(&svc)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(cidrs) != 1 {
t.Errorf("Expected exactly one CIDR: %v", cidrs.StringSlice())
}
if !IsAllowAll(cidrs) {
t.Errorf("Expected default to be allow-all: %v", cidrs.StringSlice())
}
// check SourceRanges annotation is empty
annotations := make(map[string]string)
annotations[v1.AnnotationLoadBalancerSourceRangesKey] = ""
svc = v1.Service{}
svc.Annotations = annotations
cidrs, err = GetLoadBalancerSourceRanges(&svc)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(cidrs) != 1 {
t.Errorf("Expected exactly one CIDR: %v", cidrs.StringSlice())
}
if !IsAllowAll(cidrs) {
t.Errorf("Expected default to be allow-all: %v", cidrs.StringSlice())
}
}
func TestAllowAll(t *testing.T) {
checkAllowAll := func(allowAll bool, cidrs ...string) {
ipnets, err := netsets.ParseIPNets(cidrs...)
if err != nil {
t.Errorf("Unexpected error parsing cidrs: %v", cidrs)
}
if allowAll != IsAllowAll(ipnets) {
t.Errorf("IsAllowAll did not return expected value for %v", cidrs)
}
}
checkAllowAll(false, "10.0.0.1/32")
checkAllowAll(false, "10.0.0.1/32", "10.0.0.2/32")
checkAllowAll(false, "10.0.0.1/32", "10.0.0.1/32")
checkAllowAll(true, "0.0.0.0/0")
checkAllowAll(true, "192.168.0.0/0")
checkAllowAll(true, "192.168.0.1/32", "0.0.0.0/0")
}
func TestRequestsOnlyLocalTraffic(t *testing.T) {
checkRequestsOnlyLocalTraffic := func(requestsOnlyLocalTraffic bool, service *v1.Service) {
res := RequestsOnlyLocalTraffic(service)
if res != requestsOnlyLocalTraffic {
t.Errorf("Expected requests OnlyLocal traffic = %v, got %v",
requestsOnlyLocalTraffic, res)
}
}
checkRequestsOnlyLocalTraffic(false, &v1.Service{})
checkRequestsOnlyLocalTraffic(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
},
})
checkRequestsOnlyLocalTraffic(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeNodePort,
},
})
checkRequestsOnlyLocalTraffic(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeNodePort,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
},
})
checkRequestsOnlyLocalTraffic(true, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeNodePort,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
},
})
checkRequestsOnlyLocalTraffic(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
},
})
checkRequestsOnlyLocalTraffic(true, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
},
})
}
func TestNeedsHealthCheck(t *testing.T) {
checkNeedsHealthCheck := func(needsHealthCheck bool, service *v1.Service) {
res := NeedsHealthCheck(service)
if res != needsHealthCheck {
t.Errorf("Expected needs health check = %v, got %v",
needsHealthCheck, res)
}
}
checkNeedsHealthCheck(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
},
})
checkNeedsHealthCheck(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeNodePort,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
},
})
checkNeedsHealthCheck(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeNodePort,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
},
})
checkNeedsHealthCheck(false, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
},
})
checkNeedsHealthCheck(true, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
},
})
}

47
vendor/k8s.io/kubernetes/pkg/api/v1/validation/BUILD generated vendored Normal file
View file

@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["validation.go"],
deps = [
"//pkg/api/helper:go_default_library",
"//pkg/api/v1/helper:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)

View file

@ -0,0 +1,171 @@
/*
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"
"strings"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/api/helper"
v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
)
const isNegativeErrorMsg string = `must be greater than or equal to 0`
const isNotIntegerErrorMsg string = `must be an integer`
func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
limPath := fldPath.Child("limits")
reqPath := fldPath.Child("requests")
for resourceName, quantity := range requirements.Limits {
fldPath := limPath.Key(string(resourceName))
// Validate resource name.
allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...)
// Validate resource quantity.
allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
}
for resourceName, quantity := range requirements.Requests {
fldPath := reqPath.Key(string(resourceName))
// Validate resource name.
allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...)
// Validate resource quantity.
allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
// Check that request <= limit.
limitQuantity, exists := requirements.Limits[resourceName]
if exists {
// For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal.
if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) {
allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName)))
} else if quantity.Cmp(limitQuantity) > 0 {
allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName)))
}
} else if resourceName == v1.ResourceNvidiaGPU {
allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s request", v1.ResourceNvidiaGPU)))
}
}
return allErrs
}
func validateContainerResourceName(value string, fldPath *field.Path) field.ErrorList {
allErrs := validateResourceName(value, fldPath)
if len(strings.Split(value, "/")) == 1 {
if !helper.IsStandardContainerResourceName(value) {
return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers"))
}
}
return allErrs
}
// ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource
func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
if helper.IsIntegerResourceName(resource) {
if value.MilliValue()%int64(1000) != int64(0) {
allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
}
}
return allErrs
}
// Validates that a Quantity is not negative
func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if value.Cmp(resource.Quantity{}) < 0 {
allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg))
}
return allErrs
}
// Validate compute resource typename.
// Refer to docs/design/resources.md for more details.
func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
// Opaque integer resources (OIR) deprecation began in v1.8
// TODO: Remove warning after OIR deprecation cycle.
if v1helper.IsOpaqueIntResourceName(v1.ResourceName(value)) {
glog.Errorf("DEPRECATION WARNING! Opaque integer resources are deprecated starting with v1.8: %s", value)
}
allErrs := field.ErrorList{}
for _, msg := range validation.IsQualifiedName(value) {
allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
}
if len(allErrs) != 0 {
return allErrs
}
if len(strings.Split(value, "/")) == 1 {
if !helper.IsStandardResourceName(value) {
return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified"))
}
}
return allErrs
}
func ValidatePodLogOptions(opts *v1.PodLogOptions) field.ErrorList {
allErrs := field.ErrorList{}
if opts.TailLines != nil && *opts.TailLines < 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg))
}
if opts.LimitBytes != nil && *opts.LimitBytes < 1 {
allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0"))
}
switch {
case opts.SinceSeconds != nil && opts.SinceTime != nil:
allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified"))
case opts.SinceSeconds != nil:
if *opts.SinceSeconds < 1 {
allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0"))
}
}
return allErrs
}
func AccumulateUniqueHostPorts(containers []v1.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for ci, ctr := range containers {
idxPath := fldPath.Index(ci)
portsPath := idxPath.Child("ports")
for pi := range ctr.Ports {
idxPath := portsPath.Index(pi)
port := ctr.Ports[pi].HostPort
if port == 0 {
continue
}
str := fmt.Sprintf("%d/%s", port, ctr.Ports[pi].Protocol)
if accumulator.Has(str) {
allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str))
} else {
accumulator.Insert(str)
}
}
}
return allErrs
}

View file

@ -0,0 +1,179 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestValidateResourceRequirements(t *testing.T) {
successCase := []struct {
Name string
requirements v1.ResourceRequirements
}{
{
Name: "GPU only setting Limits",
requirements: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("10"),
},
},
},
{
Name: "GPU setting Limits equals Requests",
requirements: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("10"),
},
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("10"),
},
},
},
{
Name: "Resources with GPU with Requests",
requirements: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("1"),
},
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("1"),
},
},
},
{
Name: "Resources with only Limits",
requirements: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
v1.ResourceName("my.org/resource"): resource.MustParse("10"),
},
},
},
{
Name: "Resources with only Requests",
requirements: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
v1.ResourceName("my.org/resource"): resource.MustParse("10"),
},
},
},
{
Name: "Resources with Requests Less Than Limits",
requirements: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"),
v1.ResourceName("my.org/resource"): resource.MustParse("9"),
},
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
v1.ResourceName("my.org/resource"): resource.MustParse("9"),
},
},
},
}
for _, tc := range successCase {
if errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")); len(errs) != 0 {
t.Errorf("%q unexpected error: %v", tc.Name, errs)
}
}
errorCase := []struct {
Name string
requirements v1.ResourceRequirements
}{
{
Name: "GPU only setting Requests",
requirements: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("10"),
},
},
},
{
Name: "GPU setting Limits less than Requests",
requirements: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("10"),
},
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("11"),
},
},
},
{
Name: "GPU setting Limits larger than Requests",
requirements: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("10"),
},
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceNvidiaGPU): resource.MustParse("9"),
},
},
},
{
Name: "Resources with Requests Larger Than Limits",
requirements: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
v1.ResourceName("my.org/resource"): resource.MustParse("10m"),
},
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"),
v1.ResourceName("my.org/resource"): resource.MustParse("9m"),
},
},
},
{
Name: "Invalid Resources with Requests",
requirements: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName("my.org"): resource.MustParse("10m"),
},
},
},
{
Name: "Invalid Resources with Limits",
requirements: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("my.org"): resource.MustParse("9m"),
},
},
},
}
for _, tc := range errorCase {
if errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")); len(errs) == 0 {
t.Errorf("%q expected error", tc.Name)
}
}
}