diff --git a/go.mod b/go.mod index a2a1c64b5..8e41618fe 100644 --- a/go.mod +++ b/go.mod @@ -98,6 +98,7 @@ require ( k8s.io/gengo v0.0.0-20191120174120-e74f70b9b27e // indirect k8s.io/klog v1.0.0 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a + k8s.io/kubectl v0.17.3 kubesphere.io/im v0.1.0 // indirect openpitrix.io/iam v0.1.0 // indirect openpitrix.io/openpitrix v0.4.1-0.20190920134345-4d2be6e4965c diff --git a/pkg/kapis/resources/v1alpha3/register.go b/pkg/kapis/resources/v1alpha3/register.go index cb4d6cad5..fe81be8ab 100644 --- a/pkg/kapis/resources/v1alpha3/register.go +++ b/pkg/kapis/resources/v1alpha3/register.go @@ -32,6 +32,7 @@ import ( const ( GroupName = "resources.kubesphere.io" + tagClusteredResource = "Clustered Resource" tagComponentStatus = "Component Status" tagNamespacedResource = "Namespaced Resource" @@ -47,7 +48,7 @@ func AddToContainer(c *restful.Container, informerFactory informers.InformerFact webservice.Route(webservice.GET("/{resources}"). To(handler.handleListResources). - Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}). + Metadata(restfulspec.KeyOpenAPITags, []string{tagClusteredResource}). Doc("Cluster level resources"). Param(webservice.PathParameter("resources", "cluster level resource type, e.g. pods,jobs,configmaps,services.")). Param(webservice.QueryParameter(query.ParameterName, "name used to do filtering").Required(false)). @@ -57,6 +58,14 @@ func AddToContainer(c *restful.Container, informerFactory informers.InformerFact Param(webservice.QueryParameter(query.ParameterOrderBy, "sort parameters, e.g. orderBy=createTime")). Returns(http.StatusOK, ok, api.ListResult{})) + webservice.Route(webservice.GET("/{resources}/{name}"). + To(handler.handleGetResources). + Metadata(restfulspec.KeyOpenAPITags, []string{tagClusteredResource}). + Doc("Cluster level resource"). + Param(webservice.PathParameter("resources", "cluster level resource type, e.g. pods,jobs,configmaps,services.")). + Param(webservice.PathParameter("name", "the name of the clustered resources")). + Returns(http.StatusOK, api.StatusOK, nil)) + webservice.Route(webservice.GET("/namespaces/{namespace}/{resources}"). To(handler.handleListResources). Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}). diff --git a/pkg/models/resources/v1alpha3/node/nodes.go b/pkg/models/resources/v1alpha3/node/nodes.go new file mode 100644 index 000000000..1a549cddc --- /dev/null +++ b/pkg/models/resources/v1alpha3/node/nodes.go @@ -0,0 +1,164 @@ +package node + +import ( + "fmt" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/informers" + resourceheper "k8s.io/kubectl/pkg/util/resource" + "kubesphere.io/kubesphere/pkg/api" + clusterv1alpha1 "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1" + "kubesphere.io/kubesphere/pkg/apiserver/query" + "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3" +) + +// Those annotations were added to node only for display purposes +const ( + nodeCPURequests = "node.kubesphere.io/cpu-requests" + nodeMemoryRequests = "node.kubesphere.io/memory-requests" + nodeCPULimits = "node.kubesphere.io/cpu-limits" + nodeMemoryLimits = "node.kubesphere.io/memory-limits" + nodeCPURequestsFraction = "node.kubesphere.io/cpu-requests-fraction" + nodeCPULimitsFraction = "node.kubesphere.io/cpu-limits-fraction" + nodeMemoryRequestsFraction = "node.kubesphere.io/memory-requests-fraction" + nodeMemoryLimitsFraction = "node.kubesphere.io/memory-limits-fraction" +) + +type nodesGetter struct { + informers informers.SharedInformerFactory +} + +func New(informers informers.SharedInformerFactory) v1alpha3.Interface { + return &nodesGetter{ + informers: informers, + } +} + +func (c nodesGetter) Get(_, name string) (runtime.Object, error) { + node, err := c.informers.Core().V1().Nodes().Lister().Get(name) + if err != nil { + return nil, err + } + + // ignore the error, skip annotating process if error happened + pods, _ := c.informers.Core().V1().Pods().Lister().Pods("").List(labels.Everything()) + c.annotateNode(node, pods) + + return node, nil +} + +func (c nodesGetter) List(_ string, query *query.Query) (*api.ListResult, error) { + nodes, err := c.informers.Core().V1().Nodes().Lister().List(query.Selector()) + if err != nil { + return nil, err + } + + // ignore the error, skip annotating process if error happened + pods, _ := c.informers.Core().V1().Pods().Lister().Pods("").List(labels.Everything()) + var nonTerminatedPodsList []*v1.Pod + for _, pod := range pods { + if pod.Status.Phase != v1.PodSucceeded && pod.Status.Phase != v1.PodFailed { + nonTerminatedPodsList = append(nonTerminatedPodsList, pod) + } + } + + var result []runtime.Object + for _, node := range nodes { + c.annotateNode(node, nonTerminatedPodsList) + result = append(result, node) + } + + return v1alpha3.DefaultList(result, query, c.compare, c.filter), nil +} + +func (c nodesGetter) compare(left runtime.Object, right runtime.Object, field query.Field) bool { + leftNode, ok := left.(*v1.Node) + if !ok { + return false + } + + rightNode, ok := right.(*v1.Node) + if !ok { + return false + } + + return v1alpha3.DefaultObjectMetaCompare(leftNode.ObjectMeta, rightNode.ObjectMeta, field) +} + +func (c nodesGetter) filter(object runtime.Object, filter query.Filter) bool { + cluster, ok := object.(*clusterv1alpha1.Cluster) + if !ok { + return false + } + + return v1alpha3.DefaultObjectMetaFilter(cluster.ObjectMeta, filter) +} + +// annotateNode adds cpu/memory requests usage data to node's annotations +func (c nodesGetter) annotateNode(node *v1.Node, pods []*v1.Pod) { + if len(pods) == 0 { + return + } + + var nodePods []*v1.Pod + for _, pod := range pods { + if pod.Spec.NodeName == node.Name { + nodePods = append(nodePods, pod) + } + } + + reqs, limits := c.getPodsTotalRequestAndLimits(nodePods) + + if node.Annotations == nil { + node.Annotations = make(map[string]string) + } + + cpuReqs, cpuLimits, memoryReqs, memoryLimits := reqs[v1.ResourceCPU], limits[v1.ResourceCPU], reqs[v1.ResourceMemory], limits[v1.ResourceMemory] + node.Annotations[nodeCPURequests] = cpuReqs.String() + node.Annotations[nodeCPULimits] = cpuLimits.String() + node.Annotations[nodeMemoryRequests] = memoryReqs.String() + node.Annotations[nodeMemoryLimits] = memoryLimits.String() + + fractionCpuReqs, fractionCpuLimits := float64(0), float64(0) + allocatable := node.Status.Allocatable + if allocatable.Cpu().MilliValue() != 0 { + fractionCpuReqs = float64(cpuReqs.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 + fractionCpuLimits = float64(cpuLimits.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 + } + fractionMemoryReqs, fractionMemoryLimits := float64(0), float64(0) + if allocatable.Memory().Value() != 0 { + fractionMemoryReqs = float64(memoryReqs.Value()) / float64(allocatable.Memory().Value()) * 100 + fractionMemoryLimits = float64(memoryLimits.Value()) / float64(allocatable.Memory().Value()) * 100 + } + + node.Annotations[nodeCPURequestsFraction] = fmt.Sprintf("%d%%", int(fractionCpuReqs)) + node.Annotations[nodeCPULimitsFraction] = fmt.Sprintf("%d%%", int(fractionCpuLimits)) + node.Annotations[nodeMemoryRequestsFraction] = fmt.Sprintf("%d%%", int(fractionMemoryReqs)) + node.Annotations[nodeMemoryLimitsFraction] = fmt.Sprintf("%d%%", int(fractionMemoryLimits)) +} + +func (c nodesGetter) getPodsTotalRequestAndLimits(pods []*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 _, pod := range pods { + podReqs, podLimits := resourceheper.PodRequestsAndLimits(pod) + for podReqName, podReqValue := range podReqs { + if value, ok := reqs[podReqName]; !ok { + reqs[podReqName] = podReqValue.DeepCopy() + } else { + value.Add(podReqValue) + reqs[podReqName] = value + } + } + for podLimitName, podLimitValue := range podLimits { + if value, ok := limits[podLimitName]; !ok { + limits[podLimitName] = podLimitValue.DeepCopy() + } else { + value.Add(podLimitValue) + limits[podLimitName] = value + } + } + } + return +} diff --git a/pkg/models/resources/v1alpha3/node/nodes_test.go b/pkg/models/resources/v1alpha3/node/nodes_test.go new file mode 100644 index 000000000..f60fac035 --- /dev/null +++ b/pkg/models/resources/v1alpha3/node/nodes_test.go @@ -0,0 +1,130 @@ +package node + +import ( + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "testing" +) + +// mergeResourceLists will merge resoure lists. When two lists have the same resourece, the value from +// the last list will be present in the result +func mergeResourceLists(resourceLists ...corev1.ResourceList) corev1.ResourceList { + result := corev1.ResourceList{} + for _, rl := range resourceLists { + for resource, quantity := range rl { + result[resource] = quantity + } + } + return result +} + +func getResourceList(cpu, memory string) corev1.ResourceList { + res := corev1.ResourceList{} + if cpu != "" { + res[corev1.ResourceCPU] = resource.MustParse(cpu) + } + if memory != "" { + res[corev1.ResourceMemory] = resource.MustParse(memory) + } + return res +} + +var nodeAllocatable = mergeResourceLists(getResourceList("4", "12Gi")) +var node = &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: corev1.NodeStatus{ + Allocatable: nodeAllocatable, + }, +} + +var pods = []*corev1.Pod{ + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "pod-with-resources", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + }, + Spec: corev1.PodSpec{ + NodeName: node.Name, + Containers: []corev1.Container{ + { + Name: "cpu-mem", + Image: "image:latest", + Resources: corev1.ResourceRequirements{ + Requests: getResourceList("1", "1Gi"), + Limits: getResourceList("2", "2Gi"), + }, + }, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo2", + Name: "pod-with-resources", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + }, + Spec: corev1.PodSpec{ + NodeName: node.Name, + Containers: []corev1.Container{ + { + Name: "cpu-mem", + Image: "image:latest", + Resources: corev1.ResourceRequirements{ + Requests: getResourceList("1", "1Gi"), + Limits: getResourceList("2", "2Gi"), + }, + }, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, +} + +var expectedAnnotations = map[string]string{ + nodeCPURequests: "2", + nodeCPULimits: "4", + nodeCPURequestsFraction: "50%", + nodeCPULimitsFraction: "100%", + nodeMemoryRequests: "2Gi", + nodeMemoryLimits: "4Gi", + nodeMemoryRequestsFraction: "16%", + nodeMemoryLimitsFraction: "33%", +} + +func TestNodesGetterGet(t *testing.T) { + fake := fake.NewSimpleClientset(node, pods[0], pods[1]) + + informer := informers.NewSharedInformerFactory(fake, 0) + informer.Core().V1().Nodes().Informer().GetIndexer().Add(node) + for _, pod := range pods { + informer.Core().V1().Pods().Informer().GetIndexer().Add(pod) + } + + nodeGetter := New(informer) + got, err := nodeGetter.Get("", node.Name) + if err != nil { + t.Fatal(err) + } + nodeGot := got.(*corev1.Node) + + if diff := cmp.Diff(nodeGot.Annotations, expectedAnnotations); len(diff) != 0 { + t.Errorf("%T, diff(-got, +expected), %v", expectedAnnotations, nodeGot.Annotations) + } + +} diff --git a/pkg/models/resources/v1alpha3/resource/resource.go b/pkg/models/resources/v1alpha3/resource/resource.go index d195513ab..4f583be3a 100644 --- a/pkg/models/resources/v1alpha3/resource/resource.go +++ b/pkg/models/resources/v1alpha3/resource/resource.go @@ -39,6 +39,7 @@ import ( "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/globalrole" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/namespace" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/networkpolicy" + "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/node" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/persistentvolumeclaim" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/pod" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/role" @@ -61,6 +62,7 @@ func NewResourceGetter(factory informers.InformerFactory) *ResourceGetter { getters[schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}] = namespace.New(factory.KubernetesSharedInformerFactory()) getters[schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}] = configmap.New(factory.KubernetesSharedInformerFactory()) getters[schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}] = pod.New(factory.KubernetesSharedInformerFactory()) + getters[schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes"}] = node.New(factory.KubernetesSharedInformerFactory()) getters[schema.GroupVersionResource{Group: "app.k8s.io", Version: "v1beta1", Resource: "applications"}] = application.New(factory.ApplicationSharedInformerFactory()) getters[schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "networkpolicies"}] = networkpolicy.New(factory.KubernetesSharedInformerFactory()) getters[tenantv1alpha1.SchemeGroupVersion.WithResource(tenantv1alpha1.ResourcePluralWorkspace)] = workspace.New(factory.KubeSphereSharedInformerFactory()) diff --git a/vendor/k8s.io/kubectl/LICENSE b/vendor/k8s.io/kubectl/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/vendor/k8s.io/kubectl/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/vendor/k8s.io/kubectl/pkg/util/resource/resource.go b/vendor/k8s.io/kubectl/pkg/util/resource/resource.go new file mode 100644 index 000000000..8308f1583 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/resource/resource.go @@ -0,0 +1,152 @@ +/* +Copyright 2018 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" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" +) + +// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all +// containers of the pod. If pod overhead is non-nil, the pod overhead is added to the +// total container resource requests and to the total container limits which have a +// non-zero quantity. +func PodRequestsAndLimits(pod *corev1.Pod) (reqs, limits corev1.ResourceList) { + reqs, limits = corev1.ResourceList{}, corev1.ResourceList{} + for _, container := range pod.Spec.Containers { + addResourceList(reqs, container.Resources.Requests) + addResourceList(limits, container.Resources.Limits) + } + // init containers define the minimum of any resource + for _, container := range pod.Spec.InitContainers { + maxResourceList(reqs, container.Resources.Requests) + maxResourceList(limits, container.Resources.Limits) + } + + // Add overhead for running a pod to the sum of requests and to non-zero limits: + if pod.Spec.Overhead != nil { + addResourceList(reqs, pod.Spec.Overhead) + + for name, quantity := range pod.Spec.Overhead { + if value, ok := limits[name]; ok && !value.IsZero() { + value.Add(quantity) + limits[name] = value + } + } + } + return +} + +// addResourceList adds the resources in newList to list +func addResourceList(list, new corev1.ResourceList) { + for name, quantity := range new { + if value, ok := list[name]; !ok { + list[name] = quantity.DeepCopy() + } else { + value.Add(quantity) + list[name] = value + } + } +} + +// maxResourceList sets list to the greater of list/newList for every resource +// either list +func maxResourceList(list, new corev1.ResourceList) { + for name, quantity := range new { + if value, ok := list[name]; !ok { + list[name] = quantity.DeepCopy() + continue + } else { + if quantity.Cmp(value) > 0 { + list[name] = quantity.DeepCopy() + } + } + } +} + +// ExtractContainerResourceValue extracts the value of a resource +// in an already known container +func ExtractContainerResourceValue(fs *corev1.ResourceFieldSelector, container *corev1.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 +} + +var standardContainerResources = sets.NewString( + string(corev1.ResourceCPU), + string(corev1.ResourceMemory), + string(corev1.ResourceEphemeralStorage), +) + +// IsStandardContainerResourceName returns true if the container can make a resource request +// for the specified resource +func IsStandardContainerResourceName(str string) bool { + return standardContainerResources.Has(str) || IsHugePageResourceName(corev1.ResourceName(str)) +} + +// IsHugePageResourceName returns true if the resource name has the huge page +// resource prefix. +func IsHugePageResourceName(name corev1.ResourceName) bool { + return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0582ff522..1eb6ae99c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1332,6 +1332,8 @@ k8s.io/kube-openapi/pkg/schemaconv k8s.io/kube-openapi/pkg/util k8s.io/kube-openapi/pkg/util/proto k8s.io/kube-openapi/pkg/util/sets +# k8s.io/kubectl v0.17.3 => k8s.io/kubectl v0.17.3 +k8s.io/kubectl/pkg/util/resource # k8s.io/utils v0.0.0-20191114184206-e782cd3c129f => k8s.io/utils v0.0.0-20191114184206-e782cd3c129f k8s.io/utils/buffer k8s.io/utils/integer