Skip to content

Commit

Permalink
Merge pull request #146 from Heylosky/member-cluster-node
Browse files Browse the repository at this point in the history
Add route to get member cluster node
  • Loading branch information
karmada-bot authored Nov 20, 2024
2 parents e2c5898 + 64f2fde commit 062da64
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/api/app/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/deployment"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/ingress"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/job"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/member/node"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/namespace"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/overview"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/propagationpolicy"
Expand Down
33 changes: 33 additions & 0 deletions cmd/api/app/routes/member/node/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package node

import (
"context"
"github.com/gin-gonic/gin"
"github.com/karmada-io/dashboard/cmd/api/app/router"
"github.com/karmada-io/dashboard/cmd/api/app/types/common"
"github.com/karmada-io/dashboard/pkg/client"
"github.com/karmada-io/dashboard/pkg/resource/node"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func handleGetClusterNode(c *gin.Context) {
karmadaClient := client.InClusterKarmadaClient()
_, err := karmadaClient.ClusterV1alpha1().Clusters().Get(context.TODO(), c.Param("clustername"), metav1.GetOptions{})
if err != nil {
common.Fail(c, err)
return
}
memberClient := client.InClusterClientForMemberCluster(c.Param("clustername"))
dataSelect := common.ParseDataSelectPathParameter(c)
result, err := node.GetNodeList(memberClient, dataSelect)
if err != nil {
common.Fail(c, err)
return
}
common.Success(c, result)
}

func init() {
r := router.V1()
r.GET("/member/:clustername/node", handleGetClusterNode)
}
60 changes: 59 additions & 1 deletion pkg/client/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@ package client
import (
"errors"
"fmt"
"os"
"sync"

karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
kubeclient "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"
"os"
)

const proxyURL = "/apis/cluster.karmada.io/v1alpha1/clusters/%s/proxy/"

var (
kubernetesRestConfig *rest.Config
kubernetesApiConfig *clientcmdapi.Config
inClusterClient kubeclient.Interface
karmadaRestConfig *rest.Config
karmadaApiConfig *clientcmdapi.Config
karmadaMemberConfig *rest.Config
inClusterKarmadaClient karmadaclientset.Interface
inClusterClientForKarmadaApiServer kubeclient.Interface
inClusterClientForMemberApiServer kubeclient.Interface
memberClients sync.Map
)

type configBuilder struct {
Expand Down Expand Up @@ -185,6 +192,13 @@ func InitKarmadaConfig(options ...Option) {
os.Exit(1)
}
karmadaApiConfig = apiConfig

memberConfig, err := builder.buildRestConfig()
if err != nil {
klog.Errorf("Could not init member config: %s", err)
os.Exit(1)
}
karmadaMemberConfig = memberConfig
}

func InClusterKarmadaClient() karmadaclientset.Interface {
Expand Down Expand Up @@ -212,6 +226,13 @@ func GetKarmadaConfig() (*rest.Config, *clientcmdapi.Config, error) {
return karmadaRestConfig, karmadaApiConfig, nil
}

func GetMemberConfig() (*rest.Config, error) {
if !isKarmadaInitialized() {
return nil, fmt.Errorf("client package not initialized")
}
return karmadaMemberConfig, nil
}

func InClusterClientForKarmadaApiServer() kubeclient.Interface {
if !isKarmadaInitialized() {
return nil
Expand All @@ -233,6 +254,43 @@ func InClusterClientForKarmadaApiServer() kubeclient.Interface {
return inClusterClientForKarmadaApiServer
}

func InClusterClientForMemberCluster(clusterName string) kubeclient.Interface {
if !isKarmadaInitialized() {
return nil
}

// Load and return Interface for member apiserver if already exist
if value, ok := memberClients.Load(clusterName); ok {
if inClusterClientForMemberApiServer, ok = value.(kubeclient.Interface); ok {
return inClusterClientForMemberApiServer
} else {
klog.Error("Could not get client for member apiserver")
return nil
}
}

// Client for new member apiserver
restConfig, _, err := GetKarmadaConfig()
if err != nil {
klog.ErrorS(err, "Could not get karmada restConfig")
return nil
}
memberConfig, err := GetMemberConfig()
if err != nil {
klog.ErrorS(err, "Could not get member restConfig")
return nil
}
memberConfig.Host = restConfig.Host + fmt.Sprintf(proxyURL, clusterName)
c, err := kubeclient.NewForConfig(memberConfig)
if err != nil {
klog.ErrorS(err, "Could not init kubernetes in-cluster client for member apiserver")
return nil
}
inClusterClientForMemberApiServer = c
memberClients.Store(clusterName, inClusterClientForMemberApiServer)
return inClusterClientForMemberApiServer
}

func ConvertRestConfigToAPIConfig(restConfig *rest.Config) *clientcmdapi.Config {
// 将 rest.Config 转换为 clientcmdapi.Config
clientcmdConfig := clientcmdapi.NewConfig()
Expand Down
20 changes: 20 additions & 0 deletions pkg/resource/common/resourcechannels.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,26 @@ type NodeListChannel struct {
Error chan error
}

// GetNodeListChannel returns a pair of channels to a Node list and errors that both must be read
// numReads times.
func GetNodeListChannel(client client.Interface, numReads int) NodeListChannel {

channel := NodeListChannel{
List: make(chan *v1.NodeList, numReads),
Error: make(chan error, numReads),
}

go func() {
list, err := client.CoreV1().Nodes().List(context.TODO(), helpers.ListEverything)
for i := 0; i < numReads; i++ {
channel.List <- list
channel.Error <- err
}
}()

return channel
}

// NamespaceListChannel is a list and error channels to Namespaces.
type NamespaceListChannel struct {
List chan *v1.NamespaceList
Expand Down
38 changes: 38 additions & 0 deletions pkg/resource/node/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package node

import (
"github.com/karmada-io/dashboard/pkg/dataselect"
api "k8s.io/api/core/v1"
)

type NodeCell api.Node

func (self NodeCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
switch name {
case dataselect.NameProperty:
return dataselect.StdComparableString(self.ObjectMeta.Name)
case dataselect.CreationTimestampProperty:
return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
case dataselect.NamespaceProperty:
return dataselect.StdComparableString(self.ObjectMeta.Namespace)
default:
// if name is not supported then just return a constant dummy value, sort will have no effect.
return nil
}
}

func toCells(std []api.Node) []dataselect.DataCell {
cells := make([]dataselect.DataCell, len(std))
for i := range std {
cells[i] = NodeCell(std[i])
}
return cells
}

func fromCells(cells []dataselect.DataCell) []api.Node {
std := make([]api.Node, len(cells))
for i := range std {
std[i] = api.Node(cells[i].(NodeCell))
}
return std
}
93 changes: 93 additions & 0 deletions pkg/resource/node/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package node

import (
"log"

"github.com/karmada-io/dashboard/pkg/common/errors"
"github.com/karmada-io/dashboard/pkg/common/types"
"github.com/karmada-io/dashboard/pkg/dataselect"
"github.com/karmada-io/dashboard/pkg/resource/common"
"github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

type Node struct {
ObjectMeta types.ObjectMeta `json:"objectMeta"`
TypeMeta types.TypeMeta `json:"typeMeta"`
NodeSummary *v1alpha1.NodeSummary `json:"nodeSummary,omitempty"`
Status v1.NodeStatus `json:"status"`
}

// NodeList contains a list of node.
type NodeList struct {
ListMeta types.ListMeta `json:"listMeta"`

// Unordered list of Nodes
Items []Node `json:"items"`

// List of non-critical errors, that occurred during resource retrieval.
Errors []error `json:"errors"`
}

// GetNodeList returns a list of all Nodes in all cluster.
func GetNodeList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) (*NodeList, error) {
log.Printf("Getting nodes")
channels := &common.ResourceChannels{
NodeList: common.GetNodeListChannel(client, 1),
}

return GetNodeListFromChannels(channels, dsQuery)
}

// GetNodeListFromChannels returns a list of all Nodes in the cluster reading required resource list once from the channels.
func GetNodeListFromChannels(channels *common.ResourceChannels, dsQuery *dataselect.DataSelectQuery) (*NodeList, error) {
nodes := <-channels.NodeList.List
err := <-channels.NodeList.Error
nonCriticalErrors, criticalError := errors.ExtractErrors(err)
if criticalError != nil {
return nil, criticalError
}

result := toNodeList(nodes.Items, nonCriticalErrors, dsQuery)

return result, nil
}

func toNode(meta metav1.ObjectMeta, status v1.NodeStatus) Node {
return Node{
ObjectMeta: types.NewObjectMeta(meta),
TypeMeta: types.NewTypeMeta(types.ResourceKindNode),
Status: NewStatus(status),
}
}

func toNodeList(nodes []v1.Node, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *NodeList {
result := &NodeList{
Items: make([]Node, 0),
ListMeta: types.ListMeta{TotalItems: len(nodes)},
Errors: nonCriticalErrors,
}

nodeCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(nodes), dsQuery)
nodes = fromCells(nodeCells)
result.ListMeta = types.ListMeta{TotalItems: filteredTotal}

for _, item := range nodes {
result.Items = append(result.Items, toNode(item.ObjectMeta, item.Status))
}

return result
}

func NewStatus(status v1.NodeStatus) v1.NodeStatus {
return v1.NodeStatus{
Capacity: status.Capacity,
Allocatable: status.Allocatable,
Conditions: status.Conditions,
Addresses: status.Addresses,
DaemonEndpoints: status.DaemonEndpoints,
NodeInfo: status.NodeInfo,
}
}

0 comments on commit 062da64

Please sign in to comment.