diff --git a/cmd/api/app/routes/node/handler.go b/cmd/api/app/routes/node/handler.go new file mode 100644 index 00000000..53947230 --- /dev/null +++ b/cmd/api/app/routes/node/handler.go @@ -0,0 +1,25 @@ +package node + +import ( + "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" +) + +func handleGetClusterNode(c *gin.Context) { + karmadaClient := client.InClusterClientForKarmadactlApiServer(c.Param("clustername")) + dataSelect := common.ParseDataSelectPathParameter(c) + result, err := node.GetNodeList(karmadaClient, dataSelect) + if err != nil { + common.Fail(c, err) + return + } + common.Success(c, result) +} + +func init() { + r := router.V1() + r.GET("/node/:clustername", handleGetClusterNode) +} diff --git a/pkg/client/init.go b/pkg/client/init.go index 2065e278..a4b0e1a8 100644 --- a/pkg/client/init.go +++ b/pkg/client/init.go @@ -3,22 +3,26 @@ package client import ( "errors" "fmt" + "os" + "strings" + 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" ) var ( - kubernetesRestConfig *rest.Config - kubernetesApiConfig *clientcmdapi.Config - inClusterClient kubeclient.Interface - karmadaRestConfig *rest.Config - karmadaApiConfig *clientcmdapi.Config - inClusterKarmadaClient karmadaclientset.Interface - inClusterClientForKarmadaApiServer kubeclient.Interface + kubernetesRestConfig *rest.Config + kubernetesApiConfig *clientcmdapi.Config + inClusterClient kubeclient.Interface + karmadaRestConfig *rest.Config + karmadaApiConfig *clientcmdapi.Config + inClusterKarmadaClient karmadaclientset.Interface + inClusterClientForKarmadaApiServer kubeclient.Interface + inClusterClientForKarmadactlApiServer kubeclient.Interface + clusterHost string ) type configBuilder struct { @@ -185,6 +189,8 @@ func InitKarmadaConfig(options ...Option) { os.Exit(1) } karmadaApiConfig = apiConfig + + clusterHost = karmadaRestConfig.Host } func InClusterKarmadaClient() karmadaclientset.Interface { @@ -233,6 +239,29 @@ func InClusterClientForKarmadaApiServer() kubeclient.Interface { return inClusterClientForKarmadaApiServer } +func InClusterClientForKarmadactlApiServer(clustername string) kubeclient.Interface { + if !isKarmadaInitialized() { + return nil + } + karmadaCtlRestConfig, _, err := GetKarmadaConfig() + if strings.Contains(karmadaCtlRestConfig.Host, clustername) { + return inClusterClientForKarmadactlApiServer + } else { + karmadaCtlRestConfig.Host = clusterHost + "/apis/cluster.karmada.io/v1alpha1/clusters/" + clustername + "/proxy" + } + if err != nil { + klog.ErrorS(err, "Could not get karmadactl restConfig") + return nil + } + c, err := kubeclient.NewForConfig(karmadaCtlRestConfig) + if err != nil { + klog.ErrorS(err, "Could not init kubernetes in-cluster client for karmadactl apiserver") + return nil + } + inClusterClientForKarmadactlApiServer = c + return inClusterClientForKarmadactlApiServer +} + func ConvertRestConfigToAPIConfig(restConfig *rest.Config) *clientcmdapi.Config { // 将 rest.Config 转换为 clientcmdapi.Config clientcmdConfig := clientcmdapi.NewConfig() diff --git a/pkg/resource/common/resourcechannels.go b/pkg/resource/common/resourcechannels.go index 9096b1c8..3fa560bf 100644 --- a/pkg/resource/common/resourcechannels.go +++ b/pkg/resource/common/resourcechannels.go @@ -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 diff --git a/pkg/resource/node/common.go b/pkg/resource/node/common.go new file mode 100644 index 00000000..f6de4fd3 --- /dev/null +++ b/pkg/resource/node/common.go @@ -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 +} diff --git a/pkg/resource/node/detail.go b/pkg/resource/node/detail.go new file mode 100644 index 00000000..1d33faad --- /dev/null +++ b/pkg/resource/node/detail.go @@ -0,0 +1,20 @@ +package node + +type NodeAllocatedResources struct { + // CPUCapacity is specified node CPU capacity in milicores. + CPUCapacity int64 `json:"cpuCapacity"` + CPUFraction float64 `json:"cpuFraction"` + + // MemoryCapacity is specified node memory capacity in bytes. + MemoryCapacity int64 `json:"memoryCapacity"` + MemoryFraction float64 `json:"memoryFraction"` + + // AllocatedPods in number of currently allocated pods on the node. + AllocatedPods int64 `json:"allocatedPods"` + + // PodCapacity is maximum number of pods, that can be allocated on the node. + PodCapacity int64 `json:"podCapacity"` + + // PodFraction is a fraction of pods, that can be allocated on given node. + PodFraction float64 `json:"podFraction"` +} diff --git a/pkg/resource/node/list.go b/pkg/resource/node/list.go new file mode 100644 index 00000000..d6c9535d --- /dev/null +++ b/pkg/resource/node/list.go @@ -0,0 +1,83 @@ +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"` + Ready metav1.ConditionStatus `json:"ready,omitempty"` + KubernetesVersion string `json:"kubernetesVersion,omitempty"` + NodeSummary *v1alpha1.NodeSummary `json:"nodeSummary,omitempty"` + AllocatedResources NodeAllocatedResources `json:"allocatedResources"` +} + +// 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) Node { + return Node{ + ObjectMeta: types.NewObjectMeta(meta), + TypeMeta: types.NewTypeMeta(types.ResourceKindNode), + } +} + +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)) + } + + return result +}