Unverified Commit 3cbb62b6 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #56206 from brancz/top-metrics-s

Automatic merge from submit-queue (batch tested with PRs 56206, 58525). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. kubectl: Use metrics-server for kubectl top commands **What this PR does / why we need it**: This PR implements support for the kubectl top commands to use the metrics-server as an aggregated API, instead of requesting the metrics from heapster directly. If the `metrics.k8s.io` API is not served by the apiserver, then this still falls back to the previous behavior. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes #55489 **Special notes for your reviewer**: As utilizing heapster as well as the v1alpha1 version of the metrics API is discouraged, I intentionally implemented the support very separated, so that once it is decided, that support is entirely removed, this will make it easy. **Release note**: ```release-note Support metrics API in `kubectl top` commands. ``` /cc @kubernetes/sig-instrumentation-pr-reviews @DirectXMan12 @fgrzadkowski @piosz
parents 7652c252 b1448adb
...@@ -140,6 +140,7 @@ go_library( ...@@ -140,6 +140,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
...@@ -149,6 +150,9 @@ go_library( ...@@ -149,6 +150,9 @@ go_library(
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library", "//vendor/k8s.io/client-go/transport/spdy:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
], ],
) )
...@@ -224,6 +228,7 @@ go_test( ...@@ -224,6 +228,7 @@ go_test(
"//pkg/printers:go_default_library", "//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library", "//pkg/printers/internalversion:go_default_library",
"//pkg/util/strings:go_default_library", "//pkg/util/strings:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library",
...@@ -245,11 +250,15 @@ go_test( ...@@ -245,11 +250,15 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library", "//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset/fake:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
], ],
) )
......
...@@ -40,6 +40,7 @@ go_library( ...@@ -40,6 +40,7 @@ go_library(
"//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset:go_default_library",
], ],
) )
......
...@@ -50,6 +50,7 @@ import ( ...@@ -50,6 +50,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
metricsclientset "k8s.io/metrics/pkg/client/clientset_generated/clientset"
) )
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
...@@ -245,6 +246,7 @@ type TestFactory struct { ...@@ -245,6 +246,7 @@ type TestFactory struct {
TmpDir string TmpDir string
CategoryExpander categories.CategoryExpander CategoryExpander categories.CategoryExpander
SkipDiscovery bool SkipDiscovery bool
MetricsClientSet metricsclientset.Interface
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error) ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error) UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
...@@ -315,6 +317,10 @@ func (f *FakeFactory) KubernetesClientSet() (*kubernetes.Clientset, error) { ...@@ -315,6 +317,10 @@ func (f *FakeFactory) KubernetesClientSet() (*kubernetes.Clientset, error) {
return nil, nil return nil, nil
} }
func (f *FakeFactory) MetricsClientSet() (metricsclientset.Interface, error) {
return f.tf.MetricsClientSet, f.tf.Err
}
func (f *FakeFactory) ClientSet() (internalclientset.Interface, error) { func (f *FakeFactory) ClientSet() (internalclientset.Interface, error) {
return nil, nil return nil, nil
} }
...@@ -675,6 +681,10 @@ func (f *fakeAPIFactory) KubernetesClientSet() (*kubernetes.Clientset, error) { ...@@ -675,6 +681,10 @@ func (f *fakeAPIFactory) KubernetesClientSet() (*kubernetes.Clientset, error) {
return clientset, f.tf.Err return clientset, f.tf.Err
} }
func (f *fakeAPIFactory) MetricsClientSet() (metricsclientset.Interface, error) {
return f.tf.MetricsClientSet, f.tf.Err
}
func (f *fakeAPIFactory) ClientSet() (internalclientset.Interface, error) { func (f *fakeAPIFactory) ClientSet() (internalclientset.Interface, error) {
// Swap the HTTP client out of the REST client with the fake // Swap the HTTP client out of the REST client with the fake
// version. // version.
......
...@@ -19,8 +19,10 @@ package cmd ...@@ -19,8 +19,10 @@ package cmd
import ( import (
"io" "io"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
metricsapi "k8s.io/metrics/pkg/apis/metrics"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
...@@ -30,6 +32,9 @@ import ( ...@@ -30,6 +32,9 @@ import (
type TopOptions struct{} type TopOptions struct{}
var ( var (
supportedMetricsAPIVersions = []string{
"v1beta1",
}
topLong = templates.LongDesc(i18n.T(` topLong = templates.LongDesc(i18n.T(`
Display Resource (CPU/Memory/Storage) usage. Display Resource (CPU/Memory/Storage) usage.
...@@ -51,3 +56,19 @@ func NewCmdTop(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { ...@@ -51,3 +56,19 @@ func NewCmdTop(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdTopPod(f, nil, out)) cmd.AddCommand(NewCmdTopPod(f, nil, out))
return cmd return cmd
} }
func SupportedMetricsAPIVersionAvailable(discoveredAPIGroups *metav1.APIGroupList) bool {
for _, discoveredAPIGroup := range discoveredAPIGroups.Groups {
if discoveredAPIGroup.Name != metricsapi.GroupName {
continue
}
for _, version := range discoveredAPIGroup.Versions {
for _, supportedVersion := range supportedMetricsAPIVersions {
if version.Version == supportedVersion {
return true
}
}
}
}
return false
}
...@@ -25,11 +25,15 @@ import ( ...@@ -25,11 +25,15 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/discovery"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/metricsutil" "k8s.io/kubernetes/pkg/kubectl/metricsutil"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
metricsapi "k8s.io/metrics/pkg/apis/metrics"
metricsV1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
metricsclientset "k8s.io/metrics/pkg/client/clientset_generated/clientset"
) )
// TopNodeOptions contains all the options for running the top-node cli command. // TopNodeOptions contains all the options for running the top-node cli command.
...@@ -40,6 +44,8 @@ type TopNodeOptions struct { ...@@ -40,6 +44,8 @@ type TopNodeOptions struct {
HeapsterOptions HeapsterTopOptions HeapsterOptions HeapsterTopOptions
Client *metricsutil.HeapsterMetricsClient Client *metricsutil.HeapsterMetricsClient
Printer *metricsutil.TopCmdPrinter Printer *metricsutil.TopCmdPrinter
DiscoveryClient discovery.DiscoveryInterface
MetricsClient metricsclientset.Interface
} }
type HeapsterTopOptions struct { type HeapsterTopOptions struct {
...@@ -123,8 +129,16 @@ func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] ...@@ -123,8 +129,16 @@ func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
if err != nil { if err != nil {
return err return err
} }
o.DiscoveryClient = clientset.DiscoveryClient
o.MetricsClient, err = f.MetricsClientSet()
if err != nil {
return err
}
o.NodeClient = clientset.CoreV1() o.NodeClient = clientset.CoreV1()
o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port)
o.Printer = metricsutil.NewTopCmdPrinter(out) o.Printer = metricsutil.NewTopCmdPrinter(out)
return nil return nil
} }
...@@ -138,15 +152,35 @@ func (o *TopNodeOptions) Validate() error { ...@@ -138,15 +152,35 @@ func (o *TopNodeOptions) Validate() error {
func (o TopNodeOptions) RunTopNode() error { func (o TopNodeOptions) RunTopNode() error {
var err error var err error
selector := labels.Everything().String() selector := labels.Everything()
if len(o.Selector) > 0 { if len(o.Selector) > 0 {
selector = o.Selector selector, err = labels.Parse(o.Selector)
if err != nil {
return err
}
} }
metrics, err := o.Client.GetNodeMetrics(o.ResourceName, selector)
apiGroups, err := o.DiscoveryClient.ServerGroups()
if err != nil { if err != nil {
return err return err
} }
if len(metrics) == 0 {
metricsAPIAvailable := SupportedMetricsAPIVersionAvailable(apiGroups)
metrics := &metricsapi.NodeMetricsList{}
if metricsAPIAvailable {
metrics, err = getNodeMetricsFromMetricsAPI(o.MetricsClient, o.ResourceName, selector)
if err != nil {
return err
}
} else {
metrics, err = o.Client.GetNodeMetrics(o.ResourceName, selector.String())
if err != nil {
return err
}
}
if len(metrics.Items) == 0 {
return errors.New("metrics not available yet") return errors.New("metrics not available yet")
} }
...@@ -159,7 +193,7 @@ func (o TopNodeOptions) RunTopNode() error { ...@@ -159,7 +193,7 @@ func (o TopNodeOptions) RunTopNode() error {
nodes = append(nodes, *node) nodes = append(nodes, *node)
} else { } else {
nodeList, err := o.NodeClient.Nodes().List(metav1.ListOptions{ nodeList, err := o.NodeClient.Nodes().List(metav1.ListOptions{
LabelSelector: selector, LabelSelector: selector.String(),
}) })
if err != nil { if err != nil {
return err return err
...@@ -173,5 +207,30 @@ func (o TopNodeOptions) RunTopNode() error { ...@@ -173,5 +207,30 @@ func (o TopNodeOptions) RunTopNode() error {
allocatable[n.Name] = n.Status.Allocatable allocatable[n.Name] = n.Status.Allocatable
} }
return o.Printer.PrintNodeMetrics(metrics, allocatable) return o.Printer.PrintNodeMetrics(metrics.Items, allocatable)
}
func getNodeMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, resourceName string, selector labels.Selector) (*metricsapi.NodeMetricsList, error) {
var err error
versionedMetrics := &metricsV1beta1api.NodeMetricsList{}
mc := metricsClient.Metrics()
nm := mc.NodeMetricses()
if resourceName != "" {
m, err := nm.Get(resourceName, metav1.GetOptions{})
if err != nil {
return nil, err
}
versionedMetrics.Items = []metricsV1beta1api.NodeMetrics{*m}
} else {
versionedMetrics, err = nm.List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
}
}
metrics := &metricsapi.NodeMetricsList{}
err = metricsV1beta1api.Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(versionedMetrics, metrics, nil)
if err != nil {
return nil, err
}
return metrics, nil
} }
...@@ -25,11 +25,15 @@ import ( ...@@ -25,11 +25,15 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/discovery"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/metricsutil" "k8s.io/kubernetes/pkg/kubectl/metricsutil"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
metricsapi "k8s.io/metrics/pkg/apis/metrics"
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
metricsclientset "k8s.io/metrics/pkg/client/clientset_generated/clientset"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
...@@ -45,6 +49,8 @@ type TopPodOptions struct { ...@@ -45,6 +49,8 @@ type TopPodOptions struct {
HeapsterOptions HeapsterTopOptions HeapsterOptions HeapsterTopOptions
Client *metricsutil.HeapsterMetricsClient Client *metricsutil.HeapsterMetricsClient
Printer *metricsutil.TopCmdPrinter Printer *metricsutil.TopCmdPrinter
DiscoveryClient discovery.DiscoveryInterface
MetricsClient metricsclientset.Interface
} }
const metricsCreationDelay = 2 * time.Minute const metricsCreationDelay = 2 * time.Minute
...@@ -119,8 +125,16 @@ func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s ...@@ -119,8 +125,16 @@ func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s
if err != nil { if err != nil {
return err return err
} }
o.DiscoveryClient = clientset.DiscoveryClient
o.MetricsClient, err = f.MetricsClientSet()
if err != nil {
return err
}
o.PodClient = clientset.CoreV1() o.PodClient = clientset.CoreV1()
o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port)
o.Printer = metricsutil.NewTopCmdPrinter(out) o.Printer = metricsutil.NewTopCmdPrinter(out)
return nil return nil
} }
...@@ -141,10 +155,30 @@ func (o TopPodOptions) RunTopPod() error { ...@@ -141,10 +155,30 @@ func (o TopPodOptions) RunTopPod() error {
return err return err
} }
} }
metrics, err := o.Client.GetPodMetrics(o.Namespace, o.ResourceName, o.AllNamespaces, selector)
apiGroups, err := o.DiscoveryClient.ServerGroups()
if err != nil {
return err
}
metricsAPIAvailable := SupportedMetricsAPIVersionAvailable(apiGroups)
metrics := &metricsapi.PodMetricsList{}
if metricsAPIAvailable {
metrics, err = getMetricsFromMetricsAPI(o.MetricsClient, o.Namespace, o.ResourceName, o.AllNamespaces, selector)
if err != nil {
return err
}
} else {
metrics, err = o.Client.GetPodMetrics(o.Namespace, o.ResourceName, o.AllNamespaces, selector)
if err != nil {
return err
}
}
// TODO: Refactor this once Heapster becomes the API server. // TODO: Refactor this once Heapster becomes the API server.
// First we check why no metrics have been received. // First we check why no metrics have been received.
if len(metrics) == 0 { if len(metrics.Items) == 0 {
// If the API server query is successful but all the pods are newly created, // If the API server query is successful but all the pods are newly created,
// the metrics are probably not ready yet, so we return the error here in the first place. // the metrics are probably not ready yet, so we return the error here in the first place.
e := verifyEmptyMetrics(o, selector) e := verifyEmptyMetrics(o, selector)
...@@ -155,7 +189,35 @@ func (o TopPodOptions) RunTopPod() error { ...@@ -155,7 +189,35 @@ func (o TopPodOptions) RunTopPod() error {
if err != nil { if err != nil {
return err return err
} }
return o.Printer.PrintPodMetrics(metrics, o.PrintContainers, o.AllNamespaces)
return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces)
}
func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, selector labels.Selector) (*metricsapi.PodMetricsList, error) {
var err error
ns := metav1.NamespaceAll
if !allNamespaces {
ns = namespace
}
versionedMetrics := &metricsv1beta1api.PodMetricsList{}
if resourceName != "" {
m, err := metricsClient.Metrics().PodMetricses(ns).Get(resourceName, metav1.GetOptions{})
if err != nil {
return nil, err
}
versionedMetrics.Items = []metricsv1beta1api.PodMetrics{*m}
} else {
versionedMetrics, err = metricsClient.Metrics().PodMetricses(ns).List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
}
}
metrics := &metricsapi.PodMetricsList{}
err = metricsv1beta1api.Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(versionedMetrics, metrics, nil)
if err != nil {
return nil, err
}
return metrics, nil
} }
func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error { func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error {
......
...@@ -29,12 +29,14 @@ import ( ...@@ -29,12 +29,14 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
) )
const ( const (
baseHeapsterServiceAddress = "/api/v1/namespaces/kube-system/services/http:heapster:/proxy" baseHeapsterServiceAddress = "/api/v1/namespaces/kube-system/services/http:heapster:/proxy"
baseMetricsAddress = baseHeapsterServiceAddress + "/apis/metrics" baseMetricsAddress = baseHeapsterServiceAddress + "/apis/metrics"
baseMetricsServerAddress = "/apis/metrics.k8s.io/v1beta1"
metricsApiVersion = "v1alpha1" metricsApiVersion = "v1alpha1"
) )
...@@ -58,12 +60,12 @@ func marshallBody(metrics interface{}) (io.ReadCloser, error) { ...@@ -58,12 +60,12 @@ func marshallBody(metrics interface{}) (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(result)), nil return ioutil.NopCloser(bytes.NewReader(result)), nil
} }
func testNodeMetricsData() (*metricsapi.NodeMetricsList, *v1.NodeList) { func testNodeV1alpha1MetricsData() (*metricsv1alpha1api.NodeMetricsList, *v1.NodeList) {
metrics := &metricsapi.NodeMetricsList{ metrics := &metricsv1alpha1api.NodeMetricsList{
ListMeta: metav1.ListMeta{ ListMeta: metav1.ListMeta{
ResourceVersion: "1", ResourceVersion: "1",
}, },
Items: []metricsapi.NodeMetrics{ Items: []metricsv1alpha1api.NodeMetrics{
{ {
ObjectMeta: metav1.ObjectMeta{Name: "node1", ResourceVersion: "10"}, ObjectMeta: metav1.ObjectMeta{Name: "node1", ResourceVersion: "10"},
Window: metav1.Duration{Duration: time.Minute}, Window: metav1.Duration{Duration: time.Minute},
...@@ -113,3 +115,59 @@ func testNodeMetricsData() (*metricsapi.NodeMetricsList, *v1.NodeList) { ...@@ -113,3 +115,59 @@ func testNodeMetricsData() (*metricsapi.NodeMetricsList, *v1.NodeList) {
} }
return metrics, nodes return metrics, nodes
} }
func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeList) {
metrics := &metricsv1beta1api.NodeMetricsList{
ListMeta: metav1.ListMeta{
ResourceVersion: "1",
},
Items: []metricsv1beta1api.NodeMetrics{
{
ObjectMeta: metav1.ObjectMeta{Name: "node1", ResourceVersion: "10", Labels: map[string]string{"key": "value"}},
Window: metav1.Duration{Duration: time.Minute},
Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "node2", ResourceVersion: "11"},
Window: metav1.Duration{Duration: time.Minute},
Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(5, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(7*(1024*1024), resource.DecimalSI),
},
},
},
}
nodes := &v1.NodeList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []v1.Node{
{
ObjectMeta: metav1.ObjectMeta{Name: "node1", ResourceVersion: "10"},
Status: v1.NodeStatus{
Allocatable: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(20*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(30*(1024*1024), resource.DecimalSI),
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "node2", ResourceVersion: "11"},
Status: v1.NodeStatus{
Allocatable: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(50, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(60*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(70*(1024*1024), resource.DecimalSI),
},
},
},
},
}
return metrics, nodes
}
...@@ -81,6 +81,7 @@ go_library( ...@@ -81,6 +81,7 @@ go_library(
"//vendor/k8s.io/client-go/scale:go_default_library", "//vendor/k8s.io/client-go/scale:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/util/homedir:go_default_library", "//vendor/k8s.io/client-go/util/homedir:go_default_library",
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
], ],
) )
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
oldclient "k8s.io/kubernetes/pkg/client/unversioned" oldclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/version"
metricsclientset "k8s.io/metrics/pkg/client/clientset_generated/clientset"
) )
func NewClientCache(loader clientcmd.ClientConfig, discoveryClientFactory DiscoveryClientFactory) *ClientCache { func NewClientCache(loader clientcmd.ClientConfig, discoveryClientFactory DiscoveryClientFactory) *ClientCache {
...@@ -58,6 +59,7 @@ type ClientCache struct { ...@@ -58,6 +59,7 @@ type ClientCache struct {
discoveryClient discovery.DiscoveryInterface discoveryClient discovery.DiscoveryInterface
kubernetesClientCache kubernetesClientCache kubernetesClientCache kubernetesClientCache
metricsClientCache metricsClientCache
} }
// kubernetesClientCache creates a new kubernetes.Clientset one time // kubernetesClientCache creates a new kubernetes.Clientset one time
...@@ -71,6 +73,17 @@ type kubernetesClientCache struct { ...@@ -71,6 +73,17 @@ type kubernetesClientCache struct {
err error err error
} }
// metricsClientCache creates a new metricsclientset.Clientset one time
// and then returns the result for all future requests
type metricsClientCache struct {
// once makes sure the client is only initialized once
once sync.Once
// client is the cached client value
client *metricsclientset.Clientset
// err is the cached error value
err error
}
// KubernetesClientSetForVersion returns a new kubernetes.Clientset. It will cache the value // KubernetesClientSetForVersion returns a new kubernetes.Clientset. It will cache the value
// the first time it is called and return the cached value on subsequent calls. // the first time it is called and return the cached value on subsequent calls.
// If an error is encountered the first time KubernetesClientSetForVersion is called, // If an error is encountered the first time KubernetesClientSetForVersion is called,
...@@ -87,6 +100,22 @@ func (c *ClientCache) KubernetesClientSetForVersion(requiredVersion *schema.Grou ...@@ -87,6 +100,22 @@ func (c *ClientCache) KubernetesClientSetForVersion(requiredVersion *schema.Grou
return c.kubernetesClientCache.client, c.kubernetesClientCache.err return c.kubernetesClientCache.client, c.kubernetesClientCache.err
} }
// MetricsClientSetForVersion returns a new kubernetes.Clientset. It will cache the value
// the first time it is called and return the cached value on subsequent calls.
// If an error is encountered the first time MetircsClientSetForVersion is called,
// the error will be cached.
func (c *ClientCache) MetricsClientSetForVersion(requiredVersion *schema.GroupVersion) (*metricsclientset.Clientset, error) {
c.metricsClientCache.once.Do(func() {
config, err := c.ClientConfigForVersion(requiredVersion)
if err != nil {
c.kubernetesClientCache.err = err
return
}
c.metricsClientCache.client, c.metricsClientCache.err = metricsclientset.NewForConfig(config)
})
return c.metricsClientCache.client, c.metricsClientCache.err
}
// also looks up the discovery client. We can't do this during init because the flags won't have been set // also looks up the discovery client. We can't do this during init because the flags won't have been set
// because this is constructed pre-command execution before the command tree is // because this is constructed pre-command execution before the command tree is
// even set up. Requires the lock to already be acquired // even set up. Requires the lock to already be acquired
......
...@@ -48,6 +48,7 @@ import ( ...@@ -48,6 +48,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
metricsclientset "k8s.io/metrics/pkg/client/clientset_generated/clientset"
) )
const ( const (
...@@ -95,6 +96,9 @@ type ClientAccessFactory interface { ...@@ -95,6 +96,9 @@ type ClientAccessFactory interface {
// KubernetesClientSet gives you back an external clientset // KubernetesClientSet gives you back an external clientset
KubernetesClientSet() (*kubernetes.Clientset, error) KubernetesClientSet() (*kubernetes.Clientset, error)
// MetricsClientSet gives you back an external clientset for the metrics API
MetricsClientSet() (metricsclientset.Interface, error)
// Returns a RESTClient for accessing Kubernetes resources or an error. // Returns a RESTClient for accessing Kubernetes resources or an error.
RESTClient() (*restclient.RESTClient, error) RESTClient() (*restclient.RESTClient, error)
// Returns a client.Config for accessing the Kubernetes server. // Returns a client.Config for accessing the Kubernetes server.
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
"time" "time"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metricsclientset "k8s.io/metrics/pkg/client/clientset_generated/clientset"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
...@@ -201,6 +202,10 @@ func (f *ring0Factory) KubernetesClientSet() (*kubernetes.Clientset, error) { ...@@ -201,6 +202,10 @@ func (f *ring0Factory) KubernetesClientSet() (*kubernetes.Clientset, error) {
return f.clientCache.KubernetesClientSetForVersion(nil) return f.clientCache.KubernetesClientSetForVersion(nil)
} }
func (f *ring0Factory) MetricsClientSet() (metricsclientset.Interface, error) {
return f.clientCache.MetricsClientSetForVersion(nil)
}
func (f *ring0Factory) ClientSet() (internalclientset.Interface, error) { func (f *ring0Factory) ClientSet() (internalclientset.Interface, error) {
return f.clientCache.ClientSetForVersion(nil) return f.clientCache.ClientSetForVersion(nil)
} }
......
...@@ -23,6 +23,7 @@ go_library( ...@@ -23,6 +23,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library", "//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library",
], ],
) )
......
...@@ -26,7 +26,8 @@ import ( ...@@ -26,7 +26,8 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/core/validation"
metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" metricsapi "k8s.io/metrics/pkg/apis/metrics"
metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
) )
const ( const (
...@@ -97,67 +98,73 @@ func nodeMetricsUrl(name string) (string, error) { ...@@ -97,67 +98,73 @@ func nodeMetricsUrl(name string) (string, error) {
return fmt.Sprintf("%s/nodes/%s", metricsRoot, name), nil return fmt.Sprintf("%s/nodes/%s", metricsRoot, name), nil
} }
func (cli *HeapsterMetricsClient) GetNodeMetrics(nodeName string, selector string) ([]metricsapi.NodeMetrics, error) { func (cli *HeapsterMetricsClient) GetNodeMetrics(nodeName string, selector string) (*metricsapi.NodeMetricsList, error) {
params := map[string]string{"labelSelector": selector} params := map[string]string{"labelSelector": selector}
path, err := nodeMetricsUrl(nodeName) path, err := nodeMetricsUrl(nodeName)
if err != nil { if err != nil {
return []metricsapi.NodeMetrics{}, err return nil, err
} }
resultRaw, err := GetHeapsterMetrics(cli, path, params) resultRaw, err := GetHeapsterMetrics(cli, path, params)
if err != nil { if err != nil {
return []metricsapi.NodeMetrics{}, err return nil, err
} }
metrics := make([]metricsapi.NodeMetrics, 0) versionedMetrics := metricsv1alpha1api.NodeMetricsList{}
if len(nodeName) == 0 { if len(nodeName) == 0 {
metricsList := metricsapi.NodeMetricsList{} err = json.Unmarshal(resultRaw, &versionedMetrics)
err = json.Unmarshal(resultRaw, &metricsList)
if err != nil { if err != nil {
return []metricsapi.NodeMetrics{}, fmt.Errorf("failed to unmarshall heapster response: %v", err) return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
} }
metrics = append(metrics, metricsList.Items...)
} else { } else {
var singleMetric metricsapi.NodeMetrics var singleMetric metricsv1alpha1api.NodeMetrics
err = json.Unmarshal(resultRaw, &singleMetric) err = json.Unmarshal(resultRaw, &singleMetric)
if err != nil { if err != nil {
return []metricsapi.NodeMetrics{}, fmt.Errorf("failed to unmarshall heapster response: %v", err) return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
} }
metrics = append(metrics, singleMetric) versionedMetrics.Items = []metricsv1alpha1api.NodeMetrics{singleMetric}
}
metrics := &metricsapi.NodeMetricsList{}
err = metricsv1alpha1api.Convert_v1alpha1_NodeMetricsList_To_metrics_NodeMetricsList(&versionedMetrics, metrics, nil)
if err != nil {
return nil, err
} }
return metrics, nil return metrics, nil
} }
func (cli *HeapsterMetricsClient) GetPodMetrics(namespace string, podName string, allNamespaces bool, selector labels.Selector) ([]metricsapi.PodMetrics, error) { func (cli *HeapsterMetricsClient) GetPodMetrics(namespace string, podName string, allNamespaces bool, selector labels.Selector) (*metricsapi.PodMetricsList, error) {
if allNamespaces { if allNamespaces {
namespace = metav1.NamespaceAll namespace = metav1.NamespaceAll
} }
path, err := podMetricsUrl(namespace, podName) path, err := podMetricsUrl(namespace, podName)
if err != nil { if err != nil {
return []metricsapi.PodMetrics{}, err return nil, err
} }
params := map[string]string{"labelSelector": selector.String()} params := map[string]string{"labelSelector": selector.String()}
allMetrics := make([]metricsapi.PodMetrics, 0) versionedMetrics := metricsv1alpha1api.PodMetricsList{}
resultRaw, err := GetHeapsterMetrics(cli, path, params) resultRaw, err := GetHeapsterMetrics(cli, path, params)
if err != nil { if err != nil {
return []metricsapi.PodMetrics{}, err return nil, err
} }
if len(podName) == 0 { if len(podName) == 0 {
metrics := metricsapi.PodMetricsList{} err = json.Unmarshal(resultRaw, &versionedMetrics)
err = json.Unmarshal(resultRaw, &metrics)
if err != nil { if err != nil {
return []metricsapi.PodMetrics{}, fmt.Errorf("failed to unmarshall heapster response: %v", err) return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
} }
allMetrics = append(allMetrics, metrics.Items...)
} else { } else {
var singleMetric metricsapi.PodMetrics var singleMetric metricsv1alpha1api.PodMetrics
err = json.Unmarshal(resultRaw, &singleMetric) err = json.Unmarshal(resultRaw, &singleMetric)
if err != nil { if err != nil {
return []metricsapi.PodMetrics{}, fmt.Errorf("failed to unmarshall heapster response: %v", err) return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
} }
allMetrics = append(allMetrics, singleMetric) versionedMetrics.Items = []metricsv1alpha1api.PodMetrics{singleMetric}
} }
return allMetrics, nil metrics := &metricsapi.PodMetricsList{}
err = metricsv1alpha1api.Convert_v1alpha1_PodMetricsList_To_metrics_PodMetricsList(&versionedMetrics, metrics, nil)
if err != nil {
return nil, err
}
return metrics, nil
} }
func GetHeapsterMetrics(cli *HeapsterMetricsClient, path string, params map[string]string) ([]byte, error) { func GetHeapsterMetrics(cli *HeapsterMetricsClient, path string, params map[string]string) ([]byte, error) {
......
...@@ -25,7 +25,7 @@ import ( ...@@ -25,7 +25,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" metricsapi "k8s.io/metrics/pkg/apis/metrics"
) )
var ( var (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment