diff --git a/kubernetes/typed/core/v1/BUILD b/kubernetes/typed/core/v1/BUILD index 9c0d05aa..021b07b8 100644 --- a/kubernetes/typed/core/v1/BUILD +++ b/kubernetes/typed/core/v1/BUILD @@ -47,6 +47,7 @@ go_library( "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", "//vendor/k8s.io/client-go/pkg/api:go_default_library", "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1/ref:go_default_library", "//vendor/k8s.io/client-go/pkg/apis/policy/v1beta1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", ], diff --git a/kubernetes/typed/core/v1/event_expansion.go b/kubernetes/typed/core/v1/event_expansion.go index 9b4490ea..0ad170b3 100644 --- a/kubernetes/typed/core/v1/event_expansion.go +++ b/kubernetes/typed/core/v1/event_expansion.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/pkg/api/v1/ref" ) // The EventExpansion interface allows manually adding extra methods to the EventInterface. @@ -99,7 +100,7 @@ func (e *events) PatchWithEventNamespace(incompleteEvent *v1.Event, data []byte) // object must match this event's client namespace unless the event client // was made with the "" namespace. func (e *events) Search(scheme *runtime.Scheme, objOrRef runtime.Object) (*v1.EventList, error) { - ref, err := v1.GetReference(scheme, objOrRef) + ref, err := ref.GetReference(scheme, objOrRef) if err != nil { return nil, err } diff --git a/pkg/api/v1/BUILD b/pkg/api/v1/BUILD index a7448a61..46e8c54b 100644 --- a/pkg/api/v1/BUILD +++ b/pkg/api/v1/BUILD @@ -17,9 +17,9 @@ go_library( "generate.go", "generated.pb.go", "meta.go", - "ref.go", + "objectreference.go", "register.go", - "resource_helpers.go", + "resource.go", "taint.go", "toleration.go", "types.generated.go", @@ -34,7 +34,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/github.com/ugorji/go/codec:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", diff --git a/pkg/api/v1/node/BUILD b/pkg/api/v1/node/BUILD new file mode 100644 index 00000000..793209ff --- /dev/null +++ b/pkg/api/v1/node/BUILD @@ -0,0 +1,15 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["util.go"], + tags = ["automanaged"], + deps = ["//vendor/k8s.io/client-go/pkg/api/v1:go_default_library"], +) diff --git a/pkg/api/v1/node/util.go b/pkg/api/v1/node/util.go new file mode 100644 index 00000000..7fd9d25d --- /dev/null +++ b/pkg/api/v1/node/util.go @@ -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/client-go/pkg/api/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 +} diff --git a/pkg/api/v1/objectreference.go b/pkg/api/v1/objectreference.go new file mode 100644 index 00000000..ee5335ee --- /dev/null +++ b/pkg/api/v1/objectreference.go @@ -0,0 +1,33 @@ +/* +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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// IsAnAPIObject allows clients to preemptively get a reference to an API object and pass it to places that +// intend only to get a reference to that object. This simplifies the event recording interface. +func (obj *ObjectReference) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} + +func (obj *ObjectReference) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} + +func (obj *ObjectReference) GetObjectKind() schema.ObjectKind { return obj } diff --git a/pkg/api/v1/ref/BUILD b/pkg/api/v1/ref/BUILD new file mode 100644 index 00000000..f9d8716b --- /dev/null +++ b/pkg/api/v1/ref/BUILD @@ -0,0 +1,19 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["ref.go"], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + ], +) diff --git a/pkg/api/v1/ref.go b/pkg/api/v1/ref/ref.go similarity index 80% rename from pkg/api/v1/ref.go rename to pkg/api/v1/ref/ref.go index 5d33719f..51f69555 100644 --- a/pkg/api/v1/ref.go +++ b/pkg/api/v1/ref/ref.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1 +package ref import ( "errors" @@ -22,10 +22,9 @@ import ( "net/url" "strings" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/pkg/api/v1" ) var ( @@ -38,11 +37,11 @@ var ( // object, or an error if the object doesn't follow the conventions // that would allow this. // TODO: should take a meta.Interface see http://issue.k8s.io/7127 -func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*ObjectReference, error) { +func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*v1.ObjectReference, error) { if obj == nil { return nil, ErrNilObject } - if ref, ok := obj.(*ObjectReference); ok { + if ref, ok := obj.(*v1.ObjectReference); ok { // Don't make a reference to a reference. return ref, nil } @@ -94,14 +93,14 @@ func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*ObjectReference, // only has list metadata if objectMeta == nil { - return &ObjectReference{ + return &v1.ObjectReference{ Kind: kind, APIVersion: version, ResourceVersion: listMeta.GetResourceVersion(), }, nil } - return &ObjectReference{ + return &v1.ObjectReference{ Kind: kind, APIVersion: version, Name: objectMeta.GetName(), @@ -112,7 +111,7 @@ func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*ObjectReference, } // GetPartialReference is exactly like GetReference, but allows you to set the FieldPath. -func GetPartialReference(scheme *runtime.Scheme, obj runtime.Object, fieldPath string) (*ObjectReference, error) { +func GetPartialReference(scheme *runtime.Scheme, obj runtime.Object, fieldPath string) (*v1.ObjectReference, error) { ref, err := GetReference(scheme, obj) if err != nil { return nil, err @@ -120,14 +119,3 @@ func GetPartialReference(scheme *runtime.Scheme, obj runtime.Object, fieldPath s ref.FieldPath = fieldPath return ref, nil } - -// IsAnAPIObject allows clients to preemptively get a reference to an API object and pass it to places that -// intend only to get a reference to that object. This simplifies the event recording interface. -func (obj *ObjectReference) SetGroupVersionKind(gvk schema.GroupVersionKind) { - obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() -} -func (obj *ObjectReference) GroupVersionKind() schema.GroupVersionKind { - return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) -} - -func (obj *ObjectReference) GetObjectKind() schema.ObjectKind { return obj } diff --git a/pkg/api/v1/resource.go b/pkg/api/v1/resource.go new file mode 100644 index 00000000..2dca9866 --- /dev/null +++ b/pkg/api/v1/resource.go @@ -0,0 +1,56 @@ +/* +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 v1 + +import ( + "k8s.io/apimachinery/pkg/api/resource" +) + +// Returns string version of ResourceName. +func (self ResourceName) String() string { + return string(self) +} + +// Returns the CPU limit if specified. +func (self *ResourceList) Cpu() *resource.Quantity { + if val, ok := (*self)[ResourceCPU]; ok { + return &val + } + return &resource.Quantity{Format: resource.DecimalSI} +} + +// Returns the Memory limit if specified. +func (self *ResourceList) Memory() *resource.Quantity { + if val, ok := (*self)[ResourceMemory]; ok { + return &val + } + return &resource.Quantity{Format: resource.BinarySI} +} + +func (self *ResourceList) Pods() *resource.Quantity { + if val, ok := (*self)[ResourcePods]; ok { + return &val + } + return &resource.Quantity{} +} + +func (self *ResourceList) NvidiaGPU() *resource.Quantity { + if val, ok := (*self)[ResourceNvidiaGPU]; ok { + return &val + } + return &resource.Quantity{} +} diff --git a/pkg/api/v1/resource/BUILD b/pkg/api/v1/resource/BUILD new file mode 100644 index 00000000..cb244ebc --- /dev/null +++ b/pkg/api/v1/resource/BUILD @@ -0,0 +1,32 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +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", + tags = ["automanaged"], + deps = [ + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = ["helpers.go"], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/client-go/pkg/api:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + ], +) diff --git a/pkg/api/v1/resource/helpers.go b/pkg/api/v1/resource/helpers.go new file mode 100644 index 00000000..7772ace6 --- /dev/null +++ b/pkg/api/v1/resource/helpers.go @@ -0,0 +1,200 @@ +/* +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/apimachinery/pkg/api/resource" + "k8s.io/client-go/pkg/api" + "k8s.io/client-go/pkg/api/v1" +) + +// 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, err error) { + 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) +} + +// ExtractResourceValueByContainerNameAndNodeAllocatable extracts the value of a resource +// by providing container name and node allocatable +func ExtractResourceValueByContainerNameAndNodeAllocatable(fs *v1.ResourceFieldSelector, pod *v1.Pod, containerName string, nodeAllocatable v1.ResourceList) (string, error) { + realContainer, err := findContainerInPod(pod, containerName) + if err != nil { + return "", err + } + + containerCopy, err := api.Scheme.DeepCopy(realContainer) + if err != nil { + return "", fmt.Errorf("failed to perform a deep copy of container object: %v", err) + } + + container, ok := containerCopy.(*v1.Container) + if !ok { + return "", fmt.Errorf("unexpected type returned from deep copy of container object") + } + + 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 "requests.cpu": + return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor) + case "requests.memory": + return convertResourceMemoryToString(container.Resources.Requests.Memory(), 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 +} + +// 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() + } + } + } +} diff --git a/pkg/api/v1/resource/helpers_test.go b/pkg/api/v1/resource/helpers_test.go new file mode 100644 index 00000000..4866bf3c --- /dev/null +++ b/pkg/api/v1/resource/helpers_test.go @@ -0,0 +1,182 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/client-go/pkg/api/v1" +) + +func TestResourceHelpers(t *testing.T) { + cpuLimit := resource.MustParse("10") + memoryLimit := resource.MustParse("10G") + resourceSpec := v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": cpuLimit, + "memory": memoryLimit, + "kube.io/storage": 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{ + "memory": memoryLimit, + "kube.io/storage": 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, + }, + }, + }, + } +} diff --git a/pkg/api/v1/resource_helpers.go b/pkg/api/v1/resource_helpers.go deleted file mode 100644 index b80efd18..00000000 --- a/pkg/api/v1/resource_helpers.go +++ /dev/null @@ -1,358 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1 - -import ( - "fmt" - "math" - "strconv" - "time" - - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/pkg/api" -) - -// Returns string version of ResourceName. -func (self ResourceName) String() string { - return string(self) -} - -// Returns the CPU limit if specified. -func (self *ResourceList) Cpu() *resource.Quantity { - if val, ok := (*self)[ResourceCPU]; ok { - return &val - } - return &resource.Quantity{Format: resource.DecimalSI} -} - -// Returns the Memory limit if specified. -func (self *ResourceList) Memory() *resource.Quantity { - if val, ok := (*self)[ResourceMemory]; ok { - return &val - } - return &resource.Quantity{Format: resource.BinarySI} -} - -func (self *ResourceList) Pods() *resource.Quantity { - if val, ok := (*self)[ResourcePods]; ok { - return &val - } - return &resource.Quantity{} -} - -func (self *ResourceList) NvidiaGPU() *resource.Quantity { - if val, ok := (*self)[ResourceNvidiaGPU]; ok { - return &val - } - return &resource.Quantity{} -} - -func GetContainerStatus(statuses []ContainerStatus, name string) (ContainerStatus, bool) { - for i := range statuses { - if statuses[i].Name == name { - return statuses[i], true - } - } - return ContainerStatus{}, false -} - -func GetExistingContainerStatus(statuses []ContainerStatus, name string) ContainerStatus { - for i := range statuses { - if statuses[i].Name == name { - return statuses[i] - } - } - return ContainerStatus{} -} - -// IsPodAvailable returns true if a pod is available; false otherwise. -// Precondition for an available pod is that it must be ready. On top -// of that, there are two cases when a pod can be considered available: -// 1. minReadySeconds == 0, or -// 2. LastTransitionTime (is set) + minReadySeconds < current time -func IsPodAvailable(pod *Pod, minReadySeconds int32, now metav1.Time) bool { - if !IsPodReady(pod) { - return false - } - - c := GetPodReadyCondition(pod.Status) - minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second - if minReadySeconds == 0 || !c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time) { - return true - } - return false -} - -// IsPodReady returns true if a pod is ready; false otherwise. -func IsPodReady(pod *Pod) bool { - return IsPodReadyConditionTrue(pod.Status) -} - -// IsPodReady retruns true if a pod is ready; false otherwise. -func IsPodReadyConditionTrue(status PodStatus) bool { - condition := GetPodReadyCondition(status) - return condition != nil && condition.Status == ConditionTrue -} - -// Extracts the pod ready condition from the given status and returns that. -// Returns nil if the condition is not present. -func GetPodReadyCondition(status PodStatus) *PodCondition { - _, condition := GetPodCondition(&status, PodReady) - return condition -} - -// GetPodCondition 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 GetPodCondition(status *PodStatus, conditionType PodConditionType) (int, *PodCondition) { - 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 -} - -// 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 *NodeStatus, conditionType NodeConditionType) (int, *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 -} - -// Updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the -// status has changed. -// Returns true if pod condition has changed or has been added. -func UpdatePodCondition(status *PodStatus, condition *PodCondition) bool { - condition.LastTransitionTime = metav1.Now() - // Try to find this pod condition. - conditionIndex, oldCondition := GetPodCondition(status, condition.Type) - - if oldCondition == nil { - // We are adding new pod condition. - status.Conditions = append(status.Conditions, *condition) - return true - } else { - // We are updating an existing condition, so we need to check if it has changed. - if condition.Status == oldCondition.Status { - condition.LastTransitionTime = oldCondition.LastTransitionTime - } - - isEqual := condition.Status == oldCondition.Status && - condition.Reason == oldCondition.Reason && - condition.Message == oldCondition.Message && - condition.LastProbeTime.Equal(oldCondition.LastProbeTime) && - condition.LastTransitionTime.Equal(oldCondition.LastTransitionTime) - - status.Conditions[conditionIndex] = *condition - // Return true if one of the fields have changed. - return !isEqual - } -} - -// IsNodeReady returns true if a node is ready; false otherwise. -func IsNodeReady(node *Node) bool { - for _, c := range node.Status.Conditions { - if c.Type == NodeReady { - return c.Status == ConditionTrue - } - } - return false -} - -// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all -// containers of the pod. -func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, limits map[ResourceName]resource.Quantity, err error) { - reqs, limits = map[ResourceName]resource.Quantity{}, map[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 *Pod, resource ResourceName) int64 { - if resource == ResourcePods { - return 1 - } - totalResources := int64(0) - for _, container := range pod.Spec.Containers { - if rQuantity, ok := container.Resources.Requests[resource]; ok { - if resource == 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 == 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 *ResourceFieldSelector, pod *Pod, containerName string) (string, error) { - container, err := findContainerInPod(pod, containerName) - if err != nil { - return "", err - } - return ExtractContainerResourceValue(fs, container) -} - -// ExtractResourceValueByContainerNameAndNodeAllocatable extracts the value of a resource -// by providing container name and node allocatable -func ExtractResourceValueByContainerNameAndNodeAllocatable(fs *ResourceFieldSelector, pod *Pod, containerName string, nodeAllocatable ResourceList) (string, error) { - realContainer, err := findContainerInPod(pod, containerName) - if err != nil { - return "", err - } - - containerCopy, err := api.Scheme.DeepCopy(realContainer) - if err != nil { - return "", fmt.Errorf("failed to perform a deep copy of container object: %v", err) - } - - container, ok := containerCopy.(*Container) - if !ok { - return "", fmt.Errorf("unexpected type returned from deep copy of container object") - } - - MergeContainerResourceLimits(container, nodeAllocatable) - - return ExtractContainerResourceValue(fs, container) -} - -// ExtractContainerResourceValue extracts the value of a resource -// in an already known container -func ExtractContainerResourceValue(fs *ResourceFieldSelector, container *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 "requests.cpu": - return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor) - case "requests.memory": - return convertResourceMemoryToString(container.Resources.Requests.Memory(), 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 -} - -// findContainerInPod finds a container by its name in the provided pod -func findContainerInPod(pod *Pod, containerName string) (*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 *Container, - allocatable ResourceList) { - if container.Resources.Limits == nil { - container.Resources.Limits = make(ResourceList) - } - for _, resource := range []ResourceName{ResourceCPU, ResourceMemory} { - if quantity, exists := container.Resources.Limits[resource]; !exists || quantity.IsZero() { - if cap, exists := allocatable[resource]; exists { - container.Resources.Limits[resource] = *cap.Copy() - } - } - } -} diff --git a/tools/record/BUILD b/tools/record/BUILD index 48671034..0e39f0a9 100644 --- a/tools/record/BUILD +++ b/tools/record/BUILD @@ -25,6 +25,7 @@ go_test( "//vendor/k8s.io/client-go/pkg/api:go_default_library", "//vendor/k8s.io/client-go/pkg/api/install:go_default_library", "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1/ref:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/util/clock:go_default_library", ], @@ -50,6 +51,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1/ref:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/util/clock:go_default_library", ], diff --git a/tools/record/event.go b/tools/record/event.go index 26e036be..999bd1cc 100644 --- a/tools/record/event.go +++ b/tools/record/event.go @@ -27,6 +27,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/pkg/api/v1/ref" restclient "k8s.io/client-go/rest" "k8s.io/client-go/util/clock" @@ -254,7 +255,7 @@ type recorderImpl struct { } func (recorder *recorderImpl) generateEvent(object runtime.Object, timestamp metav1.Time, eventtype, reason, message string) { - ref, err := v1.GetReference(recorder.scheme, object) + ref, err := ref.GetReference(recorder.scheme, object) if err != nil { glog.Errorf("Could not construct reference to: '%#v' due to: '%v'. Will not report event: '%v' '%v' '%v'", object, err, eventtype, reason, message) return diff --git a/tools/record/event_test.go b/tools/record/event_test.go index 8c456e6b..c50bd91f 100644 --- a/tools/record/event_test.go +++ b/tools/record/event_test.go @@ -32,6 +32,7 @@ import ( "k8s.io/client-go/pkg/api" _ "k8s.io/client-go/pkg/api/install" // To register api.Pod used in tests below "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/pkg/api/v1/ref" restclient "k8s.io/client-go/rest" "k8s.io/client-go/util/clock" ) @@ -119,8 +120,8 @@ func TestEventf(t *testing.T) { UID: "differentUid", }, } - testRef, err := v1.GetPartialReference(api.Scheme, testPod, "spec.containers[2]") - testRef2, err := v1.GetPartialReference(api.Scheme, testPod2, "spec.containers[3]") + testRef, err := ref.GetPartialReference(api.Scheme, testPod, "spec.containers[2]") + testRef2, err := ref.GetPartialReference(api.Scheme, testPod2, "spec.containers[3]") if err != nil { t.Fatal(err) } @@ -531,7 +532,7 @@ func TestEventfNoNamespace(t *testing.T) { UID: "bar", }, } - testRef, err := v1.GetPartialReference(api.Scheme, testPod, "spec.containers[2]") + testRef, err := ref.GetPartialReference(api.Scheme, testPod, "spec.containers[2]") if err != nil { t.Fatal(err) } @@ -637,8 +638,8 @@ func TestMultiSinkCache(t *testing.T) { UID: "differentUid", }, } - testRef, err := v1.GetPartialReference(api.Scheme, testPod, "spec.containers[2]") - testRef2, err := v1.GetPartialReference(api.Scheme, testPod2, "spec.containers[3]") + testRef, err := ref.GetPartialReference(api.Scheme, testPod, "spec.containers[2]") + testRef2, err := ref.GetPartialReference(api.Scheme, testPod2, "spec.containers[3]") if err != nil { t.Fatal(err) }