Added field-selector option for kubectl top pod

Kubernetes-commit: 1331c76aa3d7e5db730dc008b2ee94173ca51293
This commit is contained in:
Chok Yip Lau 2021-05-19 15:54:39 -04:00 committed by Kubernetes Publisher
parent 44fa123c77
commit 03baccf059
2 changed files with 31 additions and 14 deletions

View File

@ -24,6 +24,7 @@ import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/discovery"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@ -44,7 +45,8 @@ import (
type TopPodOptions struct {
ResourceName string
Namespace string
Selector string
LabelSelector string
FieldSelector string
SortBy string
AllNamespaces bool
PrintContainers bool
@ -106,7 +108,8 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions
},
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().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.")
@ -157,17 +160,24 @@ func (o *TopPodOptions) Validate() error {
return errors.New("--sort-by accepts only cpu or memory")
}
}
if len(o.ResourceName) > 0 && len(o.Selector) > 0 {
return errors.New("only one of NAME or --selector can be provided")
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 nil
}
func (o TopPodOptions) RunTopPod() error {
var err error
selector := labels.Everything()
if len(o.Selector) > 0 {
selector, err = labels.Parse(o.Selector)
labelSelector := labels.Everything()
if len(o.LabelSelector) > 0 {
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 {
return err
}
@ -183,7 +193,7 @@ func (o TopPodOptions) RunTopPod() error {
if !metricsAPIAvailable {
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 {
return err
}
@ -192,7 +202,7 @@ func (o TopPodOptions) RunTopPod() error {
if len(metrics.Items) == 0 {
// 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.
err := verifyEmptyMetrics(o, selector)
err := verifyEmptyMetrics(o, labelSelector, fieldSelector)
if err != nil {
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)
}
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
ns := metav1.NamespaceAll
if !allNamespaces {
@ -222,7 +232,7 @@ func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespac
}
versionedMetrics.Items = []metricsv1beta1api.PodMetrics{*m}
} 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 {
return nil, err
}
@ -235,7 +245,7 @@ func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespac
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 {
pod, err := o.PodClient.Pods(o.Namespace).Get(context.TODO(), o.ResourceName, metav1.GetOptions{})
if err != nil {
@ -246,7 +256,8 @@ func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error {
}
} else {
pods, err := o.PodClient.Pods(o.Namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: selector.String(),
LabelSelector: labelSelector.String(),
FieldSelector: fieldSelector.String(),
})
if err != nil {
return err

View File

@ -107,10 +107,16 @@ func TestTopPod(t *testing.T) {
},
{
name: "pod with label selector",
options: &TopPodOptions{Selector: "key=value"},
options: &TopPodOptions{LabelSelector: "key=value"},
expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
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",
options: &TopPodOptions{PrintContainers: true},