Merge pull request #102155 from lauchokyip/addTop

Added field-selector option for kubectl top pod
This commit is contained in:
Kubernetes Prow Robot 2021-07-06 07:48:18 -07:00 committed by GitHub
commit 7d9f476337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 31 additions and 14 deletions

View File

@ -24,6 +24,7 @@ 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/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@ -44,7 +45,8 @@ import (
type TopPodOptions struct { type TopPodOptions struct {
ResourceName string ResourceName string
Namespace string Namespace string
Selector string LabelSelector string
FieldSelector string
SortBy string SortBy string
AllNamespaces bool AllNamespaces bool
PrintContainers bool PrintContainers bool
@ -106,7 +108,8 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions
}, },
Aliases: []string{"pods", "po"}, Aliases: []string{"pods", "po"},
} }
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort pods list using specified field. The field can be either 'cpu' or 'memory'.") cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort pods list using specified field. The field can be either 'cpu' or 'memory'.")
cmd.Flags().BoolVar(&o.PrintContainers, "containers", o.PrintContainers, "If present, print usage of containers within a pod.") cmd.Flags().BoolVar(&o.PrintContainers, "containers", o.PrintContainers, "If present, print usage of containers within a pod.")
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
@ -157,17 +160,24 @@ func (o *TopPodOptions) Validate() error {
return errors.New("--sort-by accepts only cpu or memory") return errors.New("--sort-by accepts only cpu or memory")
} }
} }
if len(o.ResourceName) > 0 && len(o.Selector) > 0 { if len(o.ResourceName) > 0 && (len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0) {
return errors.New("only one of NAME or --selector can be provided") return errors.New("only one of NAME or selector can be provided")
} }
return nil return nil
} }
func (o TopPodOptions) RunTopPod() error { func (o TopPodOptions) RunTopPod() error {
var err error var err error
selector := labels.Everything() labelSelector := labels.Everything()
if len(o.Selector) > 0 { if len(o.LabelSelector) > 0 {
selector, err = labels.Parse(o.Selector) labelSelector, err = labels.Parse(o.LabelSelector)
if err != nil {
return err
}
}
fieldSelector := fields.Everything()
if len(o.FieldSelector) > 0 {
fieldSelector, err = fields.ParseSelector(o.FieldSelector)
if err != nil { if err != nil {
return err return err
} }
@ -183,7 +193,7 @@ func (o TopPodOptions) RunTopPod() error {
if !metricsAPIAvailable { if !metricsAPIAvailable {
return errors.New("Metrics API not available") return errors.New("Metrics API not available")
} }
metrics, err := getMetricsFromMetricsAPI(o.MetricsClient, o.Namespace, o.ResourceName, o.AllNamespaces, selector) metrics, err := getMetricsFromMetricsAPI(o.MetricsClient, o.Namespace, o.ResourceName, o.AllNamespaces, labelSelector, fieldSelector)
if err != nil { if err != nil {
return err return err
} }
@ -192,7 +202,7 @@ func (o TopPodOptions) RunTopPod() error {
if len(metrics.Items) == 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.
err := verifyEmptyMetrics(o, selector) err := verifyEmptyMetrics(o, labelSelector, fieldSelector)
if err != nil { if err != nil {
return err return err
} }
@ -208,7 +218,7 @@ func (o TopPodOptions) RunTopPod() error {
return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces, o.NoHeaders, o.SortBy) return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces, o.NoHeaders, o.SortBy)
} }
func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, selector labels.Selector) (*metricsapi.PodMetricsList, error) { func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, labelSelector labels.Selector, fieldSelector fields.Selector) (*metricsapi.PodMetricsList, error) {
var err error var err error
ns := metav1.NamespaceAll ns := metav1.NamespaceAll
if !allNamespaces { if !allNamespaces {
@ -222,7 +232,7 @@ func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespac
} }
versionedMetrics.Items = []metricsv1beta1api.PodMetrics{*m} versionedMetrics.Items = []metricsv1beta1api.PodMetrics{*m}
} else { } else {
versionedMetrics, err = metricsClient.MetricsV1beta1().PodMetricses(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()}) versionedMetrics, err = metricsClient.MetricsV1beta1().PodMetricses(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String(), FieldSelector: fieldSelector.String()})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -235,7 +245,7 @@ func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespac
return metrics, nil return metrics, nil
} }
func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error { func verifyEmptyMetrics(o TopPodOptions, labelSelector labels.Selector, fieldSelector fields.Selector) error {
if len(o.ResourceName) > 0 { if len(o.ResourceName) > 0 {
pod, err := o.PodClient.Pods(o.Namespace).Get(context.TODO(), o.ResourceName, metav1.GetOptions{}) pod, err := o.PodClient.Pods(o.Namespace).Get(context.TODO(), o.ResourceName, metav1.GetOptions{})
if err != nil { if err != nil {
@ -246,7 +256,8 @@ func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error {
} }
} else { } else {
pods, err := o.PodClient.Pods(o.Namespace).List(context.TODO(), metav1.ListOptions{ pods, err := o.PodClient.Pods(o.Namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: selector.String(), LabelSelector: labelSelector.String(),
FieldSelector: fieldSelector.String(),
}) })
if err != nil { if err != nil {
return err return err

View File

@ -107,10 +107,16 @@ func TestTopPod(t *testing.T) {
}, },
{ {
name: "pod with label selector", name: "pod with label selector",
options: &TopPodOptions{Selector: "key=value"}, options: &TopPodOptions{LabelSelector: "key=value"},
expectedQuery: "labelSelector=" + url.QueryEscape("key=value"), expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
namespaces: []string{testNS, testNS}, namespaces: []string{testNS, testNS},
}, },
{
name: "pod with field selector",
options: &TopPodOptions{FieldSelector: "key=value"},
expectedQuery: "fieldSelector=" + url.QueryEscape("key=value"),
namespaces: []string{testNS, testNS},
},
{ {
name: "pod with container metrics", name: "pod with container metrics",
options: &TopPodOptions{PrintContainers: true}, options: &TopPodOptions{PrintContainers: true},