Merge pull request #87498 from serathius/drop-heapster
Remove heapster support for kubectl Kubernetes-commit: d3c3907e10e4a9812a3faea00bbd3b4e61e82ac0
This commit is contained in:
commit
09dae15fbc
|
@ -572,7 +572,7 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery",
|
||||
"Rev": "48159c651603"
|
||||
"Rev": "1aec6bc431a9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/cli-runtime",
|
||||
|
|
4
go.mod
4
go.mod
|
@ -35,7 +35,7 @@ require (
|
|||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
k8s.io/api v0.0.0-20200320042356-1fc28ea2498c
|
||||
k8s.io/apimachinery v0.0.0-20200320122144-48159c651603
|
||||
k8s.io/apimachinery v0.0.0-20200324202305-1aec6bc431a9
|
||||
k8s.io/cli-runtime v0.0.0-20200323050020-c5a29d532eb1
|
||||
k8s.io/client-go v0.0.0-20200323042902-70eb4849511b
|
||||
k8s.io/component-base v0.0.0-20200323043823-bedba4b6402d
|
||||
|
@ -52,7 +52,7 @@ replace (
|
|||
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // pinned to release-branch.go1.13
|
||||
golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // pinned to release-branch.go1.13
|
||||
k8s.io/api => k8s.io/api v0.0.0-20200320042356-1fc28ea2498c
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200320122144-48159c651603
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200324202305-1aec6bc431a9
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20200323050020-c5a29d532eb1
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20200323042902-70eb4849511b
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200319201949-6bb2b634cece
|
||||
|
|
2
go.sum
2
go.sum
|
@ -315,7 +315,7 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
|||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20200320042356-1fc28ea2498c/go.mod h1:5nMyHS4bWX496fulniJ+Sws3P6GLvaP43GadMObLf58=
|
||||
k8s.io/apimachinery v0.0.0-20200320122144-48159c651603/go.mod h1:yKN3QjQfKl8UdUL9RQ+/1VkR7nIUs7w02zC5CXhD+G0=
|
||||
k8s.io/apimachinery v0.0.0-20200324202305-1aec6bc431a9/go.mod h1:yKN3QjQfKl8UdUL9RQ+/1VkR7nIUs7w02zC5CXhD+G0=
|
||||
k8s.io/cli-runtime v0.0.0-20200323050020-c5a29d532eb1/go.mod h1:wTCA9N28mT3TbLT9ZNny22D5bBGkRwgZr+dha/ewwb8=
|
||||
k8s.io/client-go v0.0.0-20200323042902-70eb4849511b/go.mod h1:64IdCZeusi2RxFpEc1R4cLo9mtiRealqqsl6ysIAFeY=
|
||||
k8s.io/code-generator v0.0.0-20200319201949-6bb2b634cece/go.mod h1:HgVCDA66DadRZIO1Ym1MX49lfUdXKOzJ8HA7GQVQTMI=
|
||||
|
|
|
@ -44,8 +44,6 @@ type TopNodeOptions struct {
|
|||
SortBy string
|
||||
NoHeaders bool
|
||||
NodeClient corev1client.CoreV1Interface
|
||||
HeapsterOptions HeapsterTopOptions
|
||||
Client *metricsutil.HeapsterMetricsClient
|
||||
Printer *metricsutil.TopCmdPrinter
|
||||
DiscoveryClient discovery.DiscoveryInterface
|
||||
MetricsClient metricsclientset.Interface
|
||||
|
@ -53,31 +51,15 @@ type TopNodeOptions struct {
|
|||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
type HeapsterTopOptions struct {
|
||||
Namespace string
|
||||
Service string
|
||||
Scheme string
|
||||
Port string
|
||||
}
|
||||
|
||||
func (o *HeapsterTopOptions) Bind(flags *pflag.FlagSet) {
|
||||
if len(o.Namespace) == 0 {
|
||||
o.Namespace = metricsutil.DefaultHeapsterNamespace
|
||||
}
|
||||
if len(o.Service) == 0 {
|
||||
o.Service = metricsutil.DefaultHeapsterService
|
||||
}
|
||||
if len(o.Scheme) == 0 {
|
||||
o.Scheme = metricsutil.DefaultHeapsterScheme
|
||||
}
|
||||
if len(o.Port) == 0 {
|
||||
o.Port = metricsutil.DefaultHeapsterPort
|
||||
}
|
||||
|
||||
flags.StringVar(&o.Namespace, "heapster-namespace", o.Namespace, "Namespace Heapster service is located in")
|
||||
flags.StringVar(&o.Service, "heapster-service", o.Service, "Name of Heapster service")
|
||||
flags.StringVar(&o.Scheme, "heapster-scheme", o.Scheme, "Scheme (http or https) to connect to Heapster as")
|
||||
flags.StringVar(&o.Port, "heapster-port", o.Port, "Port name in service to use")
|
||||
func heapsterTopOptions(flags *pflag.FlagSet) {
|
||||
flags.String("heapster-namespace", "kube-system", "Namespace Heapster service is located in")
|
||||
flags.MarkDeprecated("heapster-namespace", "This flag is currently no-op and will be deleted.")
|
||||
flags.String("heapster-service", "heapster", "Name of Heapster service")
|
||||
flags.MarkDeprecated("heapster-service", "This flag is currently no-op and will be deleted.")
|
||||
flags.String("heapster-scheme", "http", "Scheme (http or https) to connect to Heapster as")
|
||||
flags.MarkDeprecated("heapster-scheme", "This flag is currently no-op and will be deleted.")
|
||||
flags.String("heapster-port", "", "Port name in service to use")
|
||||
flags.MarkDeprecated("heapster-port", "This flag is currently no-op and will be deleted.")
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -117,8 +99,8 @@ func NewCmdTopNode(f cmdutil.Factory, o *TopNodeOptions, streams genericclioptio
|
|||
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().StringVar(&o.SortBy, "sort-by", o.Selector, "If non-empty, sort nodes list using specified field. The field can be either 'cpu' or 'memory'.")
|
||||
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers")
|
||||
heapsterTopOptions(cmd.Flags())
|
||||
|
||||
o.HeapsterOptions.Bind(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -146,7 +128,6 @@ func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
|
|||
}
|
||||
|
||||
o.NodeClient = clientset.CoreV1()
|
||||
o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port)
|
||||
|
||||
o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
|
||||
return nil
|
||||
|
@ -181,17 +162,13 @@ func (o TopNodeOptions) RunTopNode() error {
|
|||
|
||||
metricsAPIAvailable := SupportedMetricsAPIVersionAvailable(apiGroups)
|
||||
|
||||
var 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 !metricsAPIAvailable {
|
||||
return errors.New("Metrics API not available")
|
||||
}
|
||||
|
||||
metrics, err := getNodeMetricsFromMetricsAPI(o.MetricsClient, o.ResourceName, selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(metrics.Items) == 0 {
|
||||
|
|
|
@ -25,8 +25,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"net/url"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
@ -34,7 +32,6 @@ import (
|
|||
core "k8s.io/client-go/testing"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
|
||||
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
|
||||
)
|
||||
|
@ -44,388 +41,7 @@ const (
|
|||
apiVersion = "v1"
|
||||
)
|
||||
|
||||
func TestTopNodeAllMetrics(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1alpha1MetricsData()
|
||||
expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
|
||||
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == expectedMetricsPath && m == "GET":
|
||||
body, err := marshallBody(metrics)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
case p == expectedNodePath && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdTopNode(tf, nil, streams)
|
||||
cmd.Flags().Set("no-headers", "true")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// Check the presence of node names in the output.
|
||||
result := buf.String()
|
||||
for _, m := range metrics.Items {
|
||||
if !strings.Contains(result, m.Name) {
|
||||
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
|
||||
}
|
||||
}
|
||||
if strings.Contains(result, "MEMORY") {
|
||||
t.Errorf("should not print headers with --no-headers option set:\n%s\n", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopNodeAllMetricsCustomDefaults(t *testing.T) {
|
||||
customBaseHeapsterServiceAddress := "/api/v1/namespaces/custom-namespace/services/https:custom-heapster-service:/proxy"
|
||||
customBaseMetricsAddress := customBaseHeapsterServiceAddress + "/apis/metrics"
|
||||
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1alpha1MetricsData()
|
||||
expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", customBaseMetricsAddress, metricsAPIVersion)
|
||||
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == expectedMetricsPath && m == "GET":
|
||||
body, err := marshallBody(metrics)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
case p == expectedNodePath && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
opts := &TopNodeOptions{
|
||||
HeapsterOptions: HeapsterTopOptions{
|
||||
Namespace: "custom-namespace",
|
||||
Scheme: "https",
|
||||
Service: "custom-heapster-service",
|
||||
},
|
||||
IOStreams: streams,
|
||||
}
|
||||
cmd := NewCmdTopNode(tf, opts, streams)
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// Check the presence of node names in the output.
|
||||
result := buf.String()
|
||||
for _, m := range metrics.Items {
|
||||
if !strings.Contains(result, m.Name) {
|
||||
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopNodeWithNameMetrics(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1alpha1MetricsData()
|
||||
expectedMetrics := metrics.Items[0]
|
||||
expectedNode := nodes.Items[0]
|
||||
nonExpectedMetrics := metricsv1alpha1api.NodeMetricsList{
|
||||
ListMeta: metrics.ListMeta,
|
||||
Items: metrics.Items[1:],
|
||||
}
|
||||
expectedPath := fmt.Sprintf("%s/%s/nodes/%s", baseMetricsAddress, metricsAPIVersion, expectedMetrics.Name)
|
||||
expectedNodePath := fmt.Sprintf("/%s/%s/nodes/%s", apiPrefix, apiVersion, expectedMetrics.Name)
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == expectedPath && m == "GET":
|
||||
body, err := marshallBody(expectedMetrics)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
case p == expectedNodePath && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNode)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdTopNode(tf, nil, streams)
|
||||
cmd.Run(cmd, []string{expectedMetrics.Name})
|
||||
|
||||
// Check the presence of node names in the output.
|
||||
result := buf.String()
|
||||
if !strings.Contains(result, expectedMetrics.Name) {
|
||||
t.Errorf("missing metrics for %s: \n%s", expectedMetrics.Name, result)
|
||||
}
|
||||
for _, m := range nonExpectedMetrics.Items {
|
||||
if strings.Contains(result, m.Name) {
|
||||
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1alpha1MetricsData()
|
||||
expectedMetrics := metricsv1alpha1api.NodeMetricsList{
|
||||
ListMeta: metrics.ListMeta,
|
||||
Items: metrics.Items[0:1],
|
||||
}
|
||||
expectedNodes := v1.NodeList{
|
||||
ListMeta: nodes.ListMeta,
|
||||
Items: nodes.Items[0:1],
|
||||
}
|
||||
nonExpectedMetrics := metricsv1alpha1api.NodeMetricsList{
|
||||
ListMeta: metrics.ListMeta,
|
||||
Items: metrics.Items[1:],
|
||||
}
|
||||
label := "key=value"
|
||||
expectedPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
|
||||
expectedQuery := fmt.Sprintf("labelSelector=%s", url.QueryEscape(label))
|
||||
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == expectedPath && m == "GET" && q == expectedQuery:
|
||||
body, err := marshallBody(expectedMetrics)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
case p == expectedNodePath && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNodes)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdTopNode(tf, nil, streams)
|
||||
cmd.Flags().Set("selector", label)
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// Check the presence of node names in the output.
|
||||
result := buf.String()
|
||||
for _, m := range expectedMetrics.Items {
|
||||
if !strings.Contains(result, m.Name) {
|
||||
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
|
||||
}
|
||||
}
|
||||
for _, m := range nonExpectedMetrics.Items {
|
||||
if strings.Contains(result, m.Name) {
|
||||
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopNodeWithSortByCpuMetrics(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1beta1MetricsData()
|
||||
expectedMetrics := metricsv1beta1api.NodeMetricsList{
|
||||
ListMeta: metrics.ListMeta,
|
||||
Items: metrics.Items[:],
|
||||
}
|
||||
|
||||
expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
|
||||
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||
expectedNodes := []string{"node2", "node3", "node1"}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := scheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == expectedMetricsPath && m == "GET":
|
||||
body, err := marshallBody(expectedMetrics)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
case p == expectedNodePath && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdTopNode(tf, nil, streams)
|
||||
cmd.Flags().Set("sort-by", "cpu")
|
||||
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// Check the presence of node names in the output.
|
||||
result := buf.String()
|
||||
|
||||
for _, m := range expectedMetrics.Items {
|
||||
if !strings.Contains(result, m.Name) {
|
||||
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
|
||||
}
|
||||
}
|
||||
|
||||
resultLines := strings.Split(result, "\n")
|
||||
resultNodes := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
|
||||
|
||||
for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
|
||||
lineFirstColumn := strings.Split(line, " ")[0]
|
||||
resultNodes[i] = lineFirstColumn
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resultNodes, expectedNodes) {
|
||||
t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", expectedNodes, resultNodes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTopNodeWithSortByMemoryMetrics(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1beta1MetricsData()
|
||||
expectedMetrics := metricsv1beta1api.NodeMetricsList{
|
||||
ListMeta: metrics.ListMeta,
|
||||
Items: metrics.Items[:],
|
||||
}
|
||||
expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
|
||||
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||
expectedNodes := []string{"node2", "node3", "node1"}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := scheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == expectedMetricsPath && m == "GET":
|
||||
body, err := marshallBody(expectedMetrics)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
case p == expectedNodePath && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdTopNode(tf, nil, streams)
|
||||
cmd.Flags().Set("sort-by", "memory")
|
||||
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// Check the presence of node names in the output.
|
||||
result := buf.String()
|
||||
|
||||
for _, m := range expectedMetrics.Items {
|
||||
if !strings.Contains(result, m.Name) {
|
||||
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
|
||||
}
|
||||
}
|
||||
|
||||
resultLines := strings.Split(result, "\n")
|
||||
resultNodes := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
|
||||
|
||||
for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
|
||||
lineFirstColumn := strings.Split(line, " ")[0]
|
||||
resultNodes[i] = lineFirstColumn
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resultNodes, expectedNodes) {
|
||||
t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", expectedNodes, resultNodes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTopNodeAllMetricsFromMetricsServer(t *testing.T) {
|
||||
func TestTopNodeAllMetricsFrom(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
expectedMetrics, nodes := testNodeV1beta1MetricsData()
|
||||
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||
|
@ -486,7 +102,7 @@ func TestTopNodeAllMetricsFromMetricsServer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTopNodeWithNameMetricsFromMetricsServer(t *testing.T) {
|
||||
func TestTopNodeWithNameMetricsFrom(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1beta1MetricsData()
|
||||
expectedMetrics := metrics.Items[0]
|
||||
|
@ -556,7 +172,7 @@ func TestTopNodeWithNameMetricsFromMetricsServer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTopNodeWithLabelSelectorMetricsFromMetricsServer(t *testing.T) {
|
||||
func TestTopNodeWithLabelSelectorMetricsFrom(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1beta1MetricsData()
|
||||
expectedMetrics := &metricsv1beta1api.NodeMetricsList{
|
||||
|
@ -637,7 +253,7 @@ func TestTopNodeWithLabelSelectorMetricsFromMetricsServer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTopNodeWithSortByCpuMetricsFromMetricsServer(t *testing.T) {
|
||||
func TestTopNodeWithSortByCpuMetricsFrom(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1beta1MetricsData()
|
||||
expectedMetrics := &metricsv1beta1api.NodeMetricsList{
|
||||
|
@ -723,7 +339,7 @@ func TestTopNodeWithSortByCpuMetricsFromMetricsServer(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestTopNodeWithSortByMemoryMetricsFromMetricsServer(t *testing.T) {
|
||||
func TestTopNodeWithSortByMemoryMetricsFrom(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
metrics, nodes := testNodeV1beta1MetricsData()
|
||||
expectedMetrics := &metricsv1beta1api.NodeMetricsList{
|
||||
|
|
|
@ -49,8 +49,6 @@ type TopPodOptions struct {
|
|||
PrintContainers bool
|
||||
NoHeaders bool
|
||||
PodClient corev1client.PodsGetter
|
||||
HeapsterOptions HeapsterTopOptions
|
||||
Client *metricsutil.HeapsterMetricsClient
|
||||
Printer *metricsutil.TopCmdPrinter
|
||||
DiscoveryClient discovery.DiscoveryInterface
|
||||
MetricsClient metricsclientset.Interface
|
||||
|
@ -108,7 +106,6 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions
|
|||
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().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers.")
|
||||
o.HeapsterOptions.Bind(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -140,7 +137,6 @@ func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s
|
|||
}
|
||||
|
||||
o.PodClient = clientset.CoreV1()
|
||||
o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port)
|
||||
|
||||
o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
|
||||
return nil
|
||||
|
@ -175,17 +171,12 @@ func (o TopPodOptions) RunTopPod() error {
|
|||
|
||||
metricsAPIAvailable := SupportedMetricsAPIVersionAvailable(apiGroups)
|
||||
|
||||
var 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
|
||||
}
|
||||
if !metricsAPIAvailable {
|
||||
return errors.New("Metrics API not available")
|
||||
}
|
||||
metrics, err := getMetricsFromMetricsAPI(o.MetricsClient, o.Namespace, o.ResourceName, o.AllNamespaces, selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Refactor this once Heapster becomes the API server.
|
||||
|
|
|
@ -26,15 +26,11 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apiversion "k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
|
@ -46,9 +42,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
topPathPrefix = baseMetricsAddress + "/" + metricsAPIVersion
|
||||
topMetricsAPIPathPrefix = "/apis/metrics.k8s.io/v1beta1"
|
||||
apibody = `{
|
||||
apibody = `{
|
||||
"kind": "APIVersions",
|
||||
"versions": [
|
||||
"v1"
|
||||
|
@ -60,12 +54,6 @@ const (
|
|||
}
|
||||
]
|
||||
}`
|
||||
// This is not the full output one would usually get, just a trimmed down version.
|
||||
apisbody = `{
|
||||
"kind": "APIGroupList",
|
||||
"apiVersion": "v1",
|
||||
"groups": [{}]
|
||||
}`
|
||||
|
||||
apisbodyWithMetrics = `{
|
||||
"kind": "APIGroupList",
|
||||
|
@ -90,191 +78,12 @@ const (
|
|||
)
|
||||
|
||||
func TestTopPod(t *testing.T) {
|
||||
testNS := "testns"
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags map[string]string
|
||||
args []string
|
||||
expectedPath string
|
||||
expectedQuery string
|
||||
expectedPods []string
|
||||
namespaces []string
|
||||
containers bool
|
||||
listsNamespaces bool
|
||||
}{
|
||||
{
|
||||
name: "all namespaces",
|
||||
flags: map[string]string{"all-namespaces": "true"},
|
||||
expectedPath: topPathPrefix + "/pods",
|
||||
namespaces: []string{testNS, "secondtestns", "thirdtestns"},
|
||||
listsNamespaces: true,
|
||||
},
|
||||
{
|
||||
name: "all in namespace",
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
namespaces: []string{testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with name",
|
||||
args: []string{"pod1"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
|
||||
namespaces: []string{testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with label selector",
|
||||
flags: map[string]string{"selector": "key=value"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
|
||||
namespaces: []string{testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with container metrics",
|
||||
flags: map[string]string{"containers": "true"},
|
||||
args: []string{"pod1"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
|
||||
namespaces: []string{testNS},
|
||||
containers: true,
|
||||
},
|
||||
{
|
||||
name: "no-headers set",
|
||||
flags: map[string]string{"containers": "true", "no-headers": "true"},
|
||||
args: []string{"pod1"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
|
||||
namespaces: []string{testNS},
|
||||
containers: true,
|
||||
},
|
||||
{
|
||||
name: "pod with label sort by cpu",
|
||||
flags: map[string]string{"sort-by": "cpu"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedPods: []string{"pod2", "pod3", "pod1"},
|
||||
namespaces: []string{testNS, testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with label sort by memory",
|
||||
flags: map[string]string{"sort-by": "memory"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedPods: []string{"pod2", "pod3", "pod1"},
|
||||
namespaces: []string{testNS, testNS, testNS},
|
||||
},
|
||||
}
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Logf("Running test case: %s", testCase.name)
|
||||
metricsList := testPodMetricsData()
|
||||
var expectedMetrics []metricsv1alpha1api.PodMetrics
|
||||
var expectedContainerNames, nonExpectedMetricsNames []string
|
||||
for n, m := range metricsList {
|
||||
if n < len(testCase.namespaces) {
|
||||
m.Namespace = testCase.namespaces[n]
|
||||
expectedMetrics = append(expectedMetrics, m)
|
||||
for _, c := range m.Containers {
|
||||
expectedContainerNames = append(expectedContainerNames, c.Name)
|
||||
}
|
||||
} else {
|
||||
nonExpectedMetricsNames = append(nonExpectedMetricsNames, m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
var response interface{}
|
||||
if len(expectedMetrics) == 1 {
|
||||
response = expectedMetrics[0]
|
||||
} else {
|
||||
response = metricsv1alpha1api.PodMetricsList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "2",
|
||||
},
|
||||
Items: expectedMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == testCase.expectedPath && m == "GET" && (testCase.expectedQuery == "" || q == testCase.expectedQuery):
|
||||
body, err := marshallBody(response)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", testCase.name, err)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
default:
|
||||
t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v\nExpected path: %#v\nExpected query: %#v",
|
||||
testCase.name, req, req.URL, testCase.expectedPath, testCase.expectedQuery)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdTopPod(tf, nil, streams)
|
||||
for name, value := range testCase.flags {
|
||||
cmd.Flags().Set(name, value)
|
||||
}
|
||||
cmd.Run(cmd, testCase.args)
|
||||
|
||||
// Check the presence of pod names&namespaces/container names in the output.
|
||||
result := buf.String()
|
||||
|
||||
if testCase.containers {
|
||||
for _, containerName := range expectedContainerNames {
|
||||
if !strings.Contains(result, containerName) {
|
||||
t.Errorf("%s: missing metrics for container %s: \n%s", testCase.name, containerName, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, m := range expectedMetrics {
|
||||
if !strings.Contains(result, m.Name) {
|
||||
t.Errorf("%s: missing metrics for %s: \n%s", testCase.name, m.Name, result)
|
||||
}
|
||||
if testCase.listsNamespaces && !strings.Contains(result, m.Namespace) {
|
||||
t.Errorf("%s: missing metrics for %s/%s: \n%s", testCase.name, m.Namespace, m.Name, result)
|
||||
}
|
||||
}
|
||||
for _, name := range nonExpectedMetricsNames {
|
||||
if strings.Contains(result, name) {
|
||||
t.Errorf("%s: unexpected metrics for %s: \n%s", testCase.name, name, result)
|
||||
}
|
||||
}
|
||||
if cmdutil.GetFlagBool(cmd, "no-headers") && strings.Contains(result, "MEMORY") {
|
||||
t.Errorf("%s: unexpected headers with no-headers option set: \n%s", testCase.name, result)
|
||||
}
|
||||
if cmdutil.GetFlagString(cmd, "sort-by") == "cpu" || cmdutil.GetFlagString(cmd, "sort-by") == "memory" {
|
||||
resultLines := strings.Split(result, "\n")
|
||||
resultPods := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
|
||||
|
||||
for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
|
||||
lineFirstColumn := strings.Split(line, " ")[0]
|
||||
resultPods[i] = lineFirstColumn
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(testCase.expectedPods, resultPods) {
|
||||
t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", testCase.expectedPods, resultPods)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopPodWithMetricsServer(t *testing.T) {
|
||||
testNS := "testns"
|
||||
testCases := []struct {
|
||||
name string
|
||||
namespace string
|
||||
options *TopPodOptions
|
||||
args []string
|
||||
expectedPath string
|
||||
expectedQuery string
|
||||
expectedPods []string
|
||||
namespaces []string
|
||||
|
@ -284,47 +93,40 @@ func TestTopPodWithMetricsServer(t *testing.T) {
|
|||
{
|
||||
name: "all namespaces",
|
||||
options: &TopPodOptions{AllNamespaces: true},
|
||||
expectedPath: topMetricsAPIPathPrefix + "/pods",
|
||||
namespaces: []string{testNS, "secondtestns", "thirdtestns"},
|
||||
listsNamespaces: true,
|
||||
},
|
||||
{
|
||||
name: "all in namespace",
|
||||
expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
namespaces: []string{testNS, testNS},
|
||||
name: "all in namespace",
|
||||
namespaces: []string{testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with name",
|
||||
args: []string{"pod1"},
|
||||
expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
|
||||
namespaces: []string{testNS},
|
||||
name: "pod with name",
|
||||
args: []string{"pod1"},
|
||||
namespaces: []string{testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with label selector",
|
||||
options: &TopPodOptions{Selector: "key=value"},
|
||||
expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
|
||||
namespaces: []string{testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with container metrics",
|
||||
options: &TopPodOptions{PrintContainers: true},
|
||||
args: []string{"pod1"},
|
||||
expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
|
||||
namespaces: []string{testNS},
|
||||
containers: true,
|
||||
name: "pod with container metrics",
|
||||
options: &TopPodOptions{PrintContainers: true},
|
||||
args: []string{"pod1"},
|
||||
namespaces: []string{testNS},
|
||||
containers: true,
|
||||
},
|
||||
{
|
||||
name: "pod with label sort by cpu",
|
||||
options: &TopPodOptions{SortBy: "cpu"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedPods: []string{"pod2", "pod3", "pod1"},
|
||||
namespaces: []string{testNS, testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with label sort by memory",
|
||||
options: &TopPodOptions{SortBy: "memory"},
|
||||
expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedPods: []string{"pod2", "pod3", "pod1"},
|
||||
namespaces: []string{testNS, testNS, testNS},
|
||||
},
|
||||
|
@ -457,21 +259,18 @@ func TestTopPodNoResourcesFound(t *testing.T) {
|
|||
namespace string
|
||||
expectedOutput string
|
||||
expectedErr string
|
||||
expectedPath string
|
||||
}{
|
||||
{
|
||||
name: "all namespaces",
|
||||
options: &TopPodOptions{AllNamespaces: true},
|
||||
expectedOutput: "",
|
||||
expectedErr: "No resources found\n",
|
||||
expectedPath: topMetricsAPIPathPrefix + "/pods",
|
||||
},
|
||||
{
|
||||
name: "all in namespace",
|
||||
namespace: testNS,
|
||||
expectedOutput: "",
|
||||
expectedErr: "No resources found in " + testNS + " namespace.\n",
|
||||
expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
},
|
||||
}
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
|
@ -551,237 +350,6 @@ func TestTopPodNoResourcesFound(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type fakeDiscovery struct{}
|
||||
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
func (d *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
func (d *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServerResources returns the supported resources for all groups and versions.
|
||||
// Deprecated: use ServerGroupsAndResources instead.
|
||||
func (d *fakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServerGroupsAndResources returns the supported groups and resources for all groups and versions.
|
||||
func (d *fakeDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// ServerPreferredResources returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
func (d *fakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
func (d *fakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServerVersion retrieves and parses the server's version (git version).
|
||||
func (d *fakeDiscovery) ServerVersion() (*apiversion.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
|
||||
func (d *fakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (d *fakeDiscovery) RESTClient() restclient.Interface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTopPodCustomDefaults(t *testing.T) {
|
||||
customBaseHeapsterServiceAddress := "/api/v1/namespaces/custom-namespace/services/https:custom-heapster-service:/proxy"
|
||||
customBaseMetricsAddress := customBaseHeapsterServiceAddress + "/apis/metrics"
|
||||
customTopPathPrefix := customBaseMetricsAddress + "/" + metricsAPIVersion
|
||||
|
||||
testNS := "custom-namespace"
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags map[string]string
|
||||
args []string
|
||||
expectedPath string
|
||||
expectedQuery string
|
||||
expectedPods []string
|
||||
namespaces []string
|
||||
containers bool
|
||||
listsNamespaces bool
|
||||
}{
|
||||
{
|
||||
name: "all namespaces",
|
||||
flags: map[string]string{"all-namespaces": "true"},
|
||||
expectedPath: customTopPathPrefix + "/pods",
|
||||
namespaces: []string{testNS, "secondtestns", "thirdtestns"},
|
||||
listsNamespaces: true,
|
||||
},
|
||||
{
|
||||
name: "all in namespace",
|
||||
expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
namespaces: []string{testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with name",
|
||||
args: []string{"pod1"},
|
||||
expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
|
||||
namespaces: []string{testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with label selector",
|
||||
flags: map[string]string{"selector": "key=value"},
|
||||
expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
|
||||
namespaces: []string{testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with container metrics",
|
||||
flags: map[string]string{"containers": "true"},
|
||||
args: []string{"pod1"},
|
||||
expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
|
||||
namespaces: []string{testNS},
|
||||
containers: true,
|
||||
},
|
||||
{
|
||||
name: "pod with label sort by cpu",
|
||||
flags: map[string]string{"sort-by": "cpu"},
|
||||
expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedPods: []string{"pod2", "pod3", "pod1"},
|
||||
namespaces: []string{testNS, testNS, testNS},
|
||||
},
|
||||
{
|
||||
name: "pod with label sort by memory",
|
||||
flags: map[string]string{"sort-by": "memory"},
|
||||
expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods",
|
||||
expectedPods: []string{"pod2", "pod3", "pod1"},
|
||||
namespaces: []string{testNS, testNS, testNS},
|
||||
},
|
||||
}
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Logf("Running test case: %s", testCase.name)
|
||||
metricsList := testPodMetricsData()
|
||||
var expectedMetrics []metricsv1alpha1api.PodMetrics
|
||||
var expectedContainerNames, nonExpectedMetricsNames []string
|
||||
for n, m := range metricsList {
|
||||
if n < len(testCase.namespaces) {
|
||||
m.Namespace = testCase.namespaces[n]
|
||||
expectedMetrics = append(expectedMetrics, m)
|
||||
for _, c := range m.Containers {
|
||||
expectedContainerNames = append(expectedContainerNames, c.Name)
|
||||
}
|
||||
} else {
|
||||
nonExpectedMetricsNames = append(nonExpectedMetricsNames, m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
var response interface{}
|
||||
if len(expectedMetrics) == 1 {
|
||||
response = expectedMetrics[0]
|
||||
} else {
|
||||
response = metricsv1alpha1api.PodMetricsList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "2",
|
||||
},
|
||||
Items: expectedMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; {
|
||||
case p == "/api":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
|
||||
case p == "/apis":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
|
||||
case p == testCase.expectedPath && m == "GET" && (testCase.expectedQuery == "" || q == testCase.expectedQuery):
|
||||
body, err := marshallBody(response)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", testCase.name, err)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||
default:
|
||||
t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v\nExpected path: %#v\nExpected query: %#v",
|
||||
testCase.name, req, req.URL, testCase.expectedPath, testCase.expectedQuery)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
opts := &TopPodOptions{
|
||||
HeapsterOptions: HeapsterTopOptions{
|
||||
Namespace: "custom-namespace",
|
||||
Scheme: "https",
|
||||
Service: "custom-heapster-service",
|
||||
},
|
||||
DiscoveryClient: &fakeDiscovery{},
|
||||
IOStreams: streams,
|
||||
}
|
||||
cmd := NewCmdTopPod(tf, opts, streams)
|
||||
for name, value := range testCase.flags {
|
||||
cmd.Flags().Set(name, value)
|
||||
}
|
||||
cmd.Run(cmd, testCase.args)
|
||||
|
||||
// Check the presence of pod names&namespaces/container names in the output.
|
||||
result := buf.String()
|
||||
if testCase.containers {
|
||||
for _, containerName := range expectedContainerNames {
|
||||
if !strings.Contains(result, containerName) {
|
||||
t.Errorf("%s: missing metrics for container %s: \n%s", testCase.name, containerName, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, m := range expectedMetrics {
|
||||
if !strings.Contains(result, m.Name) {
|
||||
t.Errorf("%s: missing metrics for %s: \n%s", testCase.name, m.Name, result)
|
||||
}
|
||||
if testCase.listsNamespaces && !strings.Contains(result, m.Namespace) {
|
||||
t.Errorf("%s: missing metrics for %s/%s: \n%s", testCase.name, m.Namespace, m.Name, result)
|
||||
}
|
||||
}
|
||||
for _, name := range nonExpectedMetricsNames {
|
||||
if strings.Contains(result, name) {
|
||||
t.Errorf("%s: unexpected metrics for %s: \n%s", testCase.name, name, result)
|
||||
}
|
||||
}
|
||||
if cmdutil.GetFlagString(cmd, "sort-by") == "cpu" || cmdutil.GetFlagString(cmd, "sort-by") == "memory" {
|
||||
resultLines := strings.Split(result, "\n")
|
||||
resultPods := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
|
||||
|
||||
for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
|
||||
lineFirstColumn := strings.Split(line, " ")[0]
|
||||
resultPods[i] = lineFirstColumn
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(testCase.expectedPods, resultPods) {
|
||||
t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", testCase.expectedPods, resultPods)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
|
||||
return []metricsv1beta1api.PodMetrics{
|
||||
{
|
||||
|
@ -852,74 +420,3 @@ func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testPodMetricsData() []metricsv1alpha1api.PodMetrics {
|
||||
return []metricsv1alpha1api.PodMetrics{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
|
||||
Window: metav1.Duration{Duration: time.Minute},
|
||||
Containers: []metricsv1alpha1api.ContainerMetrics{
|
||||
{
|
||||
Name: "container1-1",
|
||||
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),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "container1-2",
|
||||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI),
|
||||
v1.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
|
||||
v1.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11"},
|
||||
Window: metav1.Duration{Duration: time.Minute},
|
||||
Containers: []metricsv1alpha1api.ContainerMetrics{
|
||||
{
|
||||
Name: "container2-1",
|
||||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
|
||||
v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
|
||||
v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "container2-2",
|
||||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI),
|
||||
v1.ResourceMemory: *resource.NewQuantity(11*(1024*1024), resource.DecimalSI),
|
||||
v1.ResourceStorage: *resource.NewQuantity(12*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "container2-3",
|
||||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(13, resource.DecimalSI),
|
||||
v1.ResourceMemory: *resource.NewQuantity(14*(1024*1024), resource.DecimalSI),
|
||||
v1.ResourceStorage: *resource.NewQuantity(15*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"},
|
||||
Window: metav1.Duration{Duration: time.Minute},
|
||||
Containers: []metricsv1alpha1api.ContainerMetrics{
|
||||
{
|
||||
Name: "container3-1",
|
||||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
|
||||
v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
|
||||
v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,17 +30,9 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
|
||||
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
)
|
||||
|
||||
const (
|
||||
baseHeapsterServiceAddress = "/api/v1/namespaces/kube-system/services/http:heapster:/proxy"
|
||||
baseMetricsAddress = baseHeapsterServiceAddress + "/apis/metrics"
|
||||
baseMetricsServerAddress = "/apis/metrics.k8s.io/v1beta1"
|
||||
metricsAPIVersion = "v1alpha1"
|
||||
)
|
||||
|
||||
func TestTopSubcommandsExist(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
|
||||
|
@ -61,62 +53,6 @@ func marshallBody(metrics interface{}) (io.ReadCloser, error) {
|
|||
return ioutil.NopCloser(bytes.NewReader(result)), nil
|
||||
}
|
||||
|
||||
func testNodeV1alpha1MetricsData() (*metricsv1alpha1api.NodeMetricsList, *v1.NodeList) {
|
||||
metrics := &metricsv1alpha1api.NodeMetricsList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Items: []metricsv1alpha1api.NodeMetrics{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "node1", ResourceVersion: "10"},
|
||||
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
|
||||
}
|
||||
|
||||
func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeList) {
|
||||
metrics := &metricsv1beta1api.NodeMetricsList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 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 metricsutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
metricsapi "k8s.io/metrics/pkg/apis/metrics"
|
||||
metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHeapsterNamespace = "kube-system"
|
||||
DefaultHeapsterScheme = "http"
|
||||
DefaultHeapsterService = "heapster"
|
||||
DefaultHeapsterPort = "" // use the first exposed port on the service
|
||||
)
|
||||
|
||||
var (
|
||||
prefix = "/apis"
|
||||
groupVersion = fmt.Sprintf("%s/%s", metricsGv.Group, metricsGv.Version)
|
||||
metricsRoot = fmt.Sprintf("%s/%s", prefix, groupVersion)
|
||||
|
||||
// TODO: get this from metrics api once it's finished
|
||||
metricsGv = schema.GroupVersion{Group: "metrics", Version: "v1alpha1"}
|
||||
)
|
||||
|
||||
type HeapsterMetricsClient struct {
|
||||
SVCClient corev1client.ServicesGetter
|
||||
HeapsterNamespace string
|
||||
HeapsterScheme string
|
||||
HeapsterService string
|
||||
HeapsterPort string
|
||||
}
|
||||
|
||||
func NewHeapsterMetricsClient(svcClient corev1client.ServicesGetter, namespace, scheme, service, port string) *HeapsterMetricsClient {
|
||||
return &HeapsterMetricsClient{
|
||||
SVCClient: svcClient,
|
||||
HeapsterNamespace: namespace,
|
||||
HeapsterScheme: scheme,
|
||||
HeapsterService: service,
|
||||
HeapsterPort: port,
|
||||
}
|
||||
}
|
||||
|
||||
func podMetricsURL(namespace string, name string) (string, error) {
|
||||
if namespace == metav1.NamespaceAll {
|
||||
return fmt.Sprintf("%s/pods", metricsRoot), nil
|
||||
}
|
||||
errs := validation.ValidateNamespaceName(namespace, false)
|
||||
if len(errs) > 0 {
|
||||
message := fmt.Sprintf("invalid namespace: %s - %v", namespace, errs)
|
||||
return "", errors.New(message)
|
||||
}
|
||||
if len(name) > 0 {
|
||||
errs = validation.NameIsDNSSubdomain(name, false)
|
||||
if len(errs) > 0 {
|
||||
message := fmt.Sprintf("invalid pod name: %s - %v", name, errs)
|
||||
return "", errors.New(message)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s/namespaces/%s/pods/%s", metricsRoot, namespace, name), nil
|
||||
}
|
||||
|
||||
func nodeMetricsURL(name string) (string, error) {
|
||||
if len(name) > 0 {
|
||||
errs := validation.NameIsDNSSubdomain(name, false)
|
||||
if len(errs) > 0 {
|
||||
message := fmt.Sprintf("invalid node name: %s - %v", name, errs)
|
||||
return "", errors.New(message)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s/nodes/%s", metricsRoot, name), nil
|
||||
}
|
||||
|
||||
func (cli *HeapsterMetricsClient) GetNodeMetrics(nodeName string, selector string) (*metricsapi.NodeMetricsList, error) {
|
||||
params := map[string]string{"labelSelector": selector}
|
||||
path, err := nodeMetricsURL(nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultRaw, err := GetHeapsterMetrics(cli, path, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedMetrics := metricsv1alpha1api.NodeMetricsList{}
|
||||
if len(nodeName) == 0 {
|
||||
err = json.Unmarshal(resultRaw, &versionedMetrics)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
|
||||
}
|
||||
} else {
|
||||
var singleMetric metricsv1alpha1api.NodeMetrics
|
||||
err = json.Unmarshal(resultRaw, &singleMetric)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (cli *HeapsterMetricsClient) GetPodMetrics(namespace string, podName string, allNamespaces bool, selector labels.Selector) (*metricsapi.PodMetricsList, error) {
|
||||
if allNamespaces {
|
||||
namespace = metav1.NamespaceAll
|
||||
}
|
||||
path, err := podMetricsURL(namespace, podName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := map[string]string{"labelSelector": selector.String()}
|
||||
versionedMetrics := metricsv1alpha1api.PodMetricsList{}
|
||||
|
||||
resultRaw, err := GetHeapsterMetrics(cli, path, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(podName) == 0 {
|
||||
err = json.Unmarshal(resultRaw, &versionedMetrics)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
|
||||
}
|
||||
} else {
|
||||
var singleMetric metricsv1alpha1api.PodMetrics
|
||||
err = json.Unmarshal(resultRaw, &singleMetric)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
|
||||
}
|
||||
versionedMetrics.Items = []metricsv1alpha1api.PodMetrics{singleMetric}
|
||||
}
|
||||
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) {
|
||||
return cli.SVCClient.Services(cli.HeapsterNamespace).
|
||||
ProxyGet(cli.HeapsterScheme, cli.HeapsterService, cli.HeapsterPort, path, params).
|
||||
DoRaw(context.TODO())
|
||||
}
|
Loading…
Reference in New Issue