Replace godep with dep
This commit is contained in:
parent
1e7489927c
commit
bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions
169
vendor/k8s.io/kubernetes/pkg/api/v1/backward_compatibility_test.go
generated
vendored
Normal file
169
vendor/k8s.io/kubernetes/pkg/api/v1/backward_compatibility_test.go
generated
vendored
Normal 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
224
vendor/k8s.io/kubernetes/pkg/api/v1/conversion_test.go
generated
vendored
Normal 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
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
2
vendor/k8s.io/kubernetes/pkg/api/v1/doc.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/api/v1/doc.go
generated
vendored
|
|
@ -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
41
vendor/k8s.io/kubernetes/pkg/api/v1/endpoints/BUILD
generated
vendored
Normal 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
226
vendor/k8s.io/kubernetes/pkg/api/v1/endpoints/util.go
generated
vendored
Normal 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
|
||||
}
|
||||
464
vendor/k8s.io/kubernetes/pkg/api/v1/endpoints/util_test.go
generated
vendored
Normal file
464
vendor/k8s.io/kubernetes/pkg/api/v1/endpoints/util_test.go
generated
vendored
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
590
vendor/k8s.io/kubernetes/pkg/api/v1/helper/helpers_test.go
generated
vendored
Normal file
590
vendor/k8s.io/kubernetes/pkg/api/v1/helper/helpers_test.go
generated
vendored
Normal 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
45
vendor/k8s.io/kubernetes/pkg/api/v1/helper/qos/BUILD
generated
vendored
Normal 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
98
vendor/k8s.io/kubernetes/pkg/api/v1/helper/qos/qos.go
generated
vendored
Normal 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
|
||||
}
|
||||
194
vendor/k8s.io/kubernetes/pkg/api/v1/helper/qos/qos_test.go
generated
vendored
Normal file
194
vendor/k8s.io/kubernetes/pkg/api/v1/helper/qos/qos_test.go
generated
vendored
Normal 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
25
vendor/k8s.io/kubernetes/pkg/api/v1/node/BUILD
generated
vendored
Normal 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
47
vendor/k8s.io/kubernetes/pkg/api/v1/node/util.go
generated
vendored
Normal 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
406
vendor/k8s.io/kubernetes/pkg/api/v1/pod/util_test.go
generated
vendored
Normal 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
40
vendor/k8s.io/kubernetes/pkg/api/v1/resource/BUILD
generated
vendored
Normal 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
206
vendor/k8s.io/kubernetes/pkg/api/v1/resource/helpers.go
generated
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
180
vendor/k8s.io/kubernetes/pkg/api/v1/resource/helpers_test.go
generated
vendored
Normal file
180
vendor/k8s.io/kubernetes/pkg/api/v1/resource/helpers_test.go
generated
vendored
Normal 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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
216
vendor/k8s.io/kubernetes/pkg/api/v1/service/util_test.go
generated
vendored
Normal file
216
vendor/k8s.io/kubernetes/pkg/api/v1/service/util_test.go
generated
vendored
Normal 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
47
vendor/k8s.io/kubernetes/pkg/api/v1/validation/BUILD
generated
vendored
Normal 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",
|
||||
],
|
||||
)
|
||||
171
vendor/k8s.io/kubernetes/pkg/api/v1/validation/validation.go
generated
vendored
Normal file
171
vendor/k8s.io/kubernetes/pkg/api/v1/validation/validation.go
generated
vendored
Normal 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
|
||||
}
|
||||
179
vendor/k8s.io/kubernetes/pkg/api/v1/validation/validation_test.go
generated
vendored
Normal file
179
vendor/k8s.io/kubernetes/pkg/api/v1/validation/validation_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue