Skip to content

Commit 062da64

Browse files
authored
Merge pull request karmada-io#146 from Heylosky/member-cluster-node
Add route to get member cluster node
2 parents e2c5898 + 64f2fde commit 062da64

File tree

6 files changed

+244
-1
lines changed

6 files changed

+244
-1
lines changed

cmd/api/app/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/deployment"
2626
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/ingress"
2727
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/job"
28+
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/member/node"
2829
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/namespace"
2930
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/overview"
3031
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/propagationpolicy"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package node
2+
3+
import (
4+
"context"
5+
"github.com/gin-gonic/gin"
6+
"github.com/karmada-io/dashboard/cmd/api/app/router"
7+
"github.com/karmada-io/dashboard/cmd/api/app/types/common"
8+
"github.com/karmada-io/dashboard/pkg/client"
9+
"github.com/karmada-io/dashboard/pkg/resource/node"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
)
12+
13+
func handleGetClusterNode(c *gin.Context) {
14+
karmadaClient := client.InClusterKarmadaClient()
15+
_, err := karmadaClient.ClusterV1alpha1().Clusters().Get(context.TODO(), c.Param("clustername"), metav1.GetOptions{})
16+
if err != nil {
17+
common.Fail(c, err)
18+
return
19+
}
20+
memberClient := client.InClusterClientForMemberCluster(c.Param("clustername"))
21+
dataSelect := common.ParseDataSelectPathParameter(c)
22+
result, err := node.GetNodeList(memberClient, dataSelect)
23+
if err != nil {
24+
common.Fail(c, err)
25+
return
26+
}
27+
common.Success(c, result)
28+
}
29+
30+
func init() {
31+
r := router.V1()
32+
r.GET("/member/:clustername/node", handleGetClusterNode)
33+
}

pkg/client/init.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,29 @@ package client
33
import (
44
"errors"
55
"fmt"
6+
"os"
7+
"sync"
8+
69
karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
710
kubeclient "k8s.io/client-go/kubernetes"
811
"k8s.io/client-go/rest"
912
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
1013
"k8s.io/klog/v2"
11-
"os"
1214
)
1315

16+
const proxyURL = "/apis/cluster.karmada.io/v1alpha1/clusters/%s/proxy/"
17+
1418
var (
1519
kubernetesRestConfig *rest.Config
1620
kubernetesApiConfig *clientcmdapi.Config
1721
inClusterClient kubeclient.Interface
1822
karmadaRestConfig *rest.Config
1923
karmadaApiConfig *clientcmdapi.Config
24+
karmadaMemberConfig *rest.Config
2025
inClusterKarmadaClient karmadaclientset.Interface
2126
inClusterClientForKarmadaApiServer kubeclient.Interface
27+
inClusterClientForMemberApiServer kubeclient.Interface
28+
memberClients sync.Map
2229
)
2330

2431
type configBuilder struct {
@@ -185,6 +192,13 @@ func InitKarmadaConfig(options ...Option) {
185192
os.Exit(1)
186193
}
187194
karmadaApiConfig = apiConfig
195+
196+
memberConfig, err := builder.buildRestConfig()
197+
if err != nil {
198+
klog.Errorf("Could not init member config: %s", err)
199+
os.Exit(1)
200+
}
201+
karmadaMemberConfig = memberConfig
188202
}
189203

190204
func InClusterKarmadaClient() karmadaclientset.Interface {
@@ -212,6 +226,13 @@ func GetKarmadaConfig() (*rest.Config, *clientcmdapi.Config, error) {
212226
return karmadaRestConfig, karmadaApiConfig, nil
213227
}
214228

229+
func GetMemberConfig() (*rest.Config, error) {
230+
if !isKarmadaInitialized() {
231+
return nil, fmt.Errorf("client package not initialized")
232+
}
233+
return karmadaMemberConfig, nil
234+
}
235+
215236
func InClusterClientForKarmadaApiServer() kubeclient.Interface {
216237
if !isKarmadaInitialized() {
217238
return nil
@@ -233,6 +254,43 @@ func InClusterClientForKarmadaApiServer() kubeclient.Interface {
233254
return inClusterClientForKarmadaApiServer
234255
}
235256

257+
func InClusterClientForMemberCluster(clusterName string) kubeclient.Interface {
258+
if !isKarmadaInitialized() {
259+
return nil
260+
}
261+
262+
// Load and return Interface for member apiserver if already exist
263+
if value, ok := memberClients.Load(clusterName); ok {
264+
if inClusterClientForMemberApiServer, ok = value.(kubeclient.Interface); ok {
265+
return inClusterClientForMemberApiServer
266+
} else {
267+
klog.Error("Could not get client for member apiserver")
268+
return nil
269+
}
270+
}
271+
272+
// Client for new member apiserver
273+
restConfig, _, err := GetKarmadaConfig()
274+
if err != nil {
275+
klog.ErrorS(err, "Could not get karmada restConfig")
276+
return nil
277+
}
278+
memberConfig, err := GetMemberConfig()
279+
if err != nil {
280+
klog.ErrorS(err, "Could not get member restConfig")
281+
return nil
282+
}
283+
memberConfig.Host = restConfig.Host + fmt.Sprintf(proxyURL, clusterName)
284+
c, err := kubeclient.NewForConfig(memberConfig)
285+
if err != nil {
286+
klog.ErrorS(err, "Could not init kubernetes in-cluster client for member apiserver")
287+
return nil
288+
}
289+
inClusterClientForMemberApiServer = c
290+
memberClients.Store(clusterName, inClusterClientForMemberApiServer)
291+
return inClusterClientForMemberApiServer
292+
}
293+
236294
func ConvertRestConfigToAPIConfig(restConfig *rest.Config) *clientcmdapi.Config {
237295
// 将 rest.Config 转换为 clientcmdapi.Config
238296
clientcmdConfig := clientcmdapi.NewConfig()

pkg/resource/common/resourcechannels.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,26 @@ type NodeListChannel struct {
441441
Error chan error
442442
}
443443

444+
// GetNodeListChannel returns a pair of channels to a Node list and errors that both must be read
445+
// numReads times.
446+
func GetNodeListChannel(client client.Interface, numReads int) NodeListChannel {
447+
448+
channel := NodeListChannel{
449+
List: make(chan *v1.NodeList, numReads),
450+
Error: make(chan error, numReads),
451+
}
452+
453+
go func() {
454+
list, err := client.CoreV1().Nodes().List(context.TODO(), helpers.ListEverything)
455+
for i := 0; i < numReads; i++ {
456+
channel.List <- list
457+
channel.Error <- err
458+
}
459+
}()
460+
461+
return channel
462+
}
463+
444464
// NamespaceListChannel is a list and error channels to Namespaces.
445465
type NamespaceListChannel struct {
446466
List chan *v1.NamespaceList

pkg/resource/node/common.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package node
2+
3+
import (
4+
"github.com/karmada-io/dashboard/pkg/dataselect"
5+
api "k8s.io/api/core/v1"
6+
)
7+
8+
type NodeCell api.Node
9+
10+
func (self NodeCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
11+
switch name {
12+
case dataselect.NameProperty:
13+
return dataselect.StdComparableString(self.ObjectMeta.Name)
14+
case dataselect.CreationTimestampProperty:
15+
return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
16+
case dataselect.NamespaceProperty:
17+
return dataselect.StdComparableString(self.ObjectMeta.Namespace)
18+
default:
19+
// if name is not supported then just return a constant dummy value, sort will have no effect.
20+
return nil
21+
}
22+
}
23+
24+
func toCells(std []api.Node) []dataselect.DataCell {
25+
cells := make([]dataselect.DataCell, len(std))
26+
for i := range std {
27+
cells[i] = NodeCell(std[i])
28+
}
29+
return cells
30+
}
31+
32+
func fromCells(cells []dataselect.DataCell) []api.Node {
33+
std := make([]api.Node, len(cells))
34+
for i := range std {
35+
std[i] = api.Node(cells[i].(NodeCell))
36+
}
37+
return std
38+
}

pkg/resource/node/list.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package node
2+
3+
import (
4+
"log"
5+
6+
"github.com/karmada-io/dashboard/pkg/common/errors"
7+
"github.com/karmada-io/dashboard/pkg/common/types"
8+
"github.com/karmada-io/dashboard/pkg/dataselect"
9+
"github.com/karmada-io/dashboard/pkg/resource/common"
10+
"github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
11+
v1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/client-go/kubernetes"
14+
)
15+
16+
type Node struct {
17+
ObjectMeta types.ObjectMeta `json:"objectMeta"`
18+
TypeMeta types.TypeMeta `json:"typeMeta"`
19+
NodeSummary *v1alpha1.NodeSummary `json:"nodeSummary,omitempty"`
20+
Status v1.NodeStatus `json:"status"`
21+
}
22+
23+
// NodeList contains a list of node.
24+
type NodeList struct {
25+
ListMeta types.ListMeta `json:"listMeta"`
26+
27+
// Unordered list of Nodes
28+
Items []Node `json:"items"`
29+
30+
// List of non-critical errors, that occurred during resource retrieval.
31+
Errors []error `json:"errors"`
32+
}
33+
34+
// GetNodeList returns a list of all Nodes in all cluster.
35+
func GetNodeList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) (*NodeList, error) {
36+
log.Printf("Getting nodes")
37+
channels := &common.ResourceChannels{
38+
NodeList: common.GetNodeListChannel(client, 1),
39+
}
40+
41+
return GetNodeListFromChannels(channels, dsQuery)
42+
}
43+
44+
// GetNodeListFromChannels returns a list of all Nodes in the cluster reading required resource list once from the channels.
45+
func GetNodeListFromChannels(channels *common.ResourceChannels, dsQuery *dataselect.DataSelectQuery) (*NodeList, error) {
46+
nodes := <-channels.NodeList.List
47+
err := <-channels.NodeList.Error
48+
nonCriticalErrors, criticalError := errors.ExtractErrors(err)
49+
if criticalError != nil {
50+
return nil, criticalError
51+
}
52+
53+
result := toNodeList(nodes.Items, nonCriticalErrors, dsQuery)
54+
55+
return result, nil
56+
}
57+
58+
func toNode(meta metav1.ObjectMeta, status v1.NodeStatus) Node {
59+
return Node{
60+
ObjectMeta: types.NewObjectMeta(meta),
61+
TypeMeta: types.NewTypeMeta(types.ResourceKindNode),
62+
Status: NewStatus(status),
63+
}
64+
}
65+
66+
func toNodeList(nodes []v1.Node, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *NodeList {
67+
result := &NodeList{
68+
Items: make([]Node, 0),
69+
ListMeta: types.ListMeta{TotalItems: len(nodes)},
70+
Errors: nonCriticalErrors,
71+
}
72+
73+
nodeCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(nodes), dsQuery)
74+
nodes = fromCells(nodeCells)
75+
result.ListMeta = types.ListMeta{TotalItems: filteredTotal}
76+
77+
for _, item := range nodes {
78+
result.Items = append(result.Items, toNode(item.ObjectMeta, item.Status))
79+
}
80+
81+
return result
82+
}
83+
84+
func NewStatus(status v1.NodeStatus) v1.NodeStatus {
85+
return v1.NodeStatus{
86+
Capacity: status.Capacity,
87+
Allocatable: status.Allocatable,
88+
Conditions: status.Conditions,
89+
Addresses: status.Addresses,
90+
DaemonEndpoints: status.DaemonEndpoints,
91+
NodeInfo: status.NodeInfo,
92+
}
93+
}

0 commit comments

Comments
 (0)