From 09a1df167df750a256209eb0e3afdfbc7748e230 Mon Sep 17 00:00:00 2001 From: richardgu <277631272@qq.com> Date: Tue, 6 Dec 2022 14:41:45 +0800 Subject: [PATCH] feat(platform): add pagination for node/pods (#2201) Co-authored-by: richardgu --- pkg/platform/proxy/core/node/storage/pod.go | 168 ++++++++++++++++++ .../proxy/core/node/storage/storage.go | 4 + pkg/platform/proxy/core/rest/rest.go | 1 + 3 files changed, 173 insertions(+) create mode 100644 pkg/platform/proxy/core/node/storage/pod.go diff --git a/pkg/platform/proxy/core/node/storage/pod.go b/pkg/platform/proxy/core/node/storage/pod.go new file mode 100644 index 000000000..a44b631ad --- /dev/null +++ b/pkg/platform/proxy/core/node/storage/pod.go @@ -0,0 +1,168 @@ +/* + * Tencent is pleased to support the open source community by making TKEStack + * available. + * + * Copyright (C) 2012-2019 Tencent. All Rights Reserved. + * + * 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 + * + * https://opensource.org/licenses/Apache-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 OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package storage + +import ( + "context" + "fmt" + + "tkestack.io/tke/pkg/platform/proxy" + "tkestack.io/tke/pkg/util/apiclient" + "tkestack.io/tke/pkg/util/page" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/client-go/kubernetes" + platforminternalclient "tkestack.io/tke/api/client/clientset/internalversion/typed/platform/internalversion" +) + +// PodREST implements the REST endpoint for find pods by a node. +type PodREST struct { + rest.Storage + platformClient platforminternalclient.PlatformInterface +} + +var _ rest.GetterWithOptions = &PodREST{} +var _ rest.GroupVersionKindProvider = &PodREST{} + +// GroupVersionKind is used to specify a particular GroupVersionKind to discovery. +func (r *PodREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + return corev1.SchemeGroupVersion.WithKind("PodList") +} + +// New returns an empty object that can be used with Create and Update after +// request data has been put into it. +func (r *PodREST) New() runtime.Object { + return &corev1.PodList{} +} + +// NewConnectOptions returns versioned resource that represents proxy parameters +func (r *PodREST) NewGetOptions() (runtime.Object, bool, string) { + return &metav1.ListOptions{}, false, "" +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *PodREST) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) { + client, err := proxy.ClientSet(ctx, r.platformClient) + if err != nil { + return nil, err + } + listOpts := options.(*metav1.ListOptions) + + if apiclient.ClusterVersionIsBefore19(client) { + return listPodByExtensions(ctx, client, name, listOpts) + } + + return listPodByApps(ctx, client, name, listOpts) +} + +func listPodByExtensions(ctx context.Context, client *kubernetes.Clientset, name string, listOpts *metav1.ListOptions) (runtime.Object, error) { + if listOpts.FieldSelector == "" { + listOpts.FieldSelector = fmt.Sprintf("spec.nodeName=%s", name) + } else { + listOpts.FieldSelector = listOpts.FieldSelector + "," + fmt.Sprintf("spec.nodeName=%s", name) + } + option := listOpts.DeepCopy() + option.Limit = 0 + option.Continue = "" + podList, err := client.CoreV1().Pods(metav1.NamespaceAll).List(ctx, *option) + if err != nil { + return nil, errors.NewInternalError(err) + } + + if listOpts.Continue != "" { + start, limit, err := page.DecodeContinue(ctx, "Node", name, listOpts.Continue) + if err != nil { + return nil, err + } + newStart := start + limit + if int(newStart+limit) < len(podList.Items) { + podList.Continue, err = page.EncodeContinue(ctx, "Node", name, newStart, limit) + if err != nil { + return nil, err + } + items := podList.Items[newStart : newStart+limit] + podList.Items = items + } else { + items := podList.Items[newStart:len(podList.Items)] + podList.Items = items + } + } else if listOpts.Limit != 0 { + if int(listOpts.Limit) < len(podList.Items) { + podList.Continue, err = page.EncodeContinue(ctx, "Node", name, 0, listOpts.Limit) + if err != nil { + return nil, err + } + items := podList.Items[:listOpts.Limit] + podList.Items = items + } + } + + return podList, nil +} + +func listPodByApps(ctx context.Context, client *kubernetes.Clientset, name string, listOpts *metav1.ListOptions) (runtime.Object, error) { + // list all of the pod, by node name + if listOpts.FieldSelector == "" { + listOpts.FieldSelector = fmt.Sprintf("spec.nodeName=%s", name) + } else { + listOpts.FieldSelector = listOpts.FieldSelector + "," + fmt.Sprintf("spec.nodeName=%s", name) + } + option := listOpts.DeepCopy() + option.Limit = 0 + option.Continue = "" + podList, err := client.CoreV1().Pods(metav1.NamespaceAll).List(ctx, *option) + if err != nil { + return nil, errors.NewInternalError(err) + } + + if listOpts.Continue != "" { + start, limit, err := page.DecodeContinue(ctx, "Node", name, listOpts.Continue) + if err != nil { + return nil, err + } + newStart := start + limit + if int(newStart+limit) < len(podList.Items) { + podList.Continue, err = page.EncodeContinue(ctx, "Node", name, newStart, limit) + if err != nil { + return nil, err + } + items := podList.Items[newStart : newStart+limit] + podList.Items = items + } else { + items := podList.Items[newStart:len(podList.Items)] + podList.Items = items + } + } else if listOpts.Limit != 0 { + if int(listOpts.Limit) < len(podList.Items) { + podList.Continue, err = page.EncodeContinue(ctx, "Node", name, 0, listOpts.Limit) + if err != nil { + return nil, err + } + items := podList.Items[:listOpts.Limit] + podList.Items = items + } + } + + return podList, nil +} diff --git a/pkg/platform/proxy/core/node/storage/storage.go b/pkg/platform/proxy/core/node/storage/storage.go index 5ac831ba3..7583191e1 100644 --- a/pkg/platform/proxy/core/node/storage/storage.go +++ b/pkg/platform/proxy/core/node/storage/storage.go @@ -34,6 +34,7 @@ import ( // Storage includes storage for resources. type Storage struct { Node *REST + Pods *PodREST Status *StatusREST } @@ -55,6 +56,9 @@ func NewStorage(_ genericregistry.RESTOptionsGetter, platformClient platforminte return &Storage{ Node: &REST{nodeStore}, + Pods: &PodREST{ + platformClient: platformClient, + }, Status: &StatusREST{ store: &statusStore, }, diff --git a/pkg/platform/proxy/core/rest/rest.go b/pkg/platform/proxy/core/rest/rest.go index 62c9b90df..9efe1f7f9 100644 --- a/pkg/platform/proxy/core/rest/rest.go +++ b/pkg/platform/proxy/core/rest/rest.go @@ -108,6 +108,7 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi "namespaces/status": namespaceStore.Status, "namespaces/finalize": namespaceStore.Finalize, "nodes": nodeStore.Node, + "nodes/pods": nodeStore.Pods, "nodes/status": nodeStore.Status, "events": eventStore.Event, "secrets": secretStore.Secret,