Skip to content

Commit 7d57a89

Browse files
committed
refactor, cluster management
1 parent f1269df commit 7d57a89

File tree

8 files changed

+322
-152
lines changed

8 files changed

+322
-152
lines changed

multicluster/clusterinfo/context.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ type contextKey string
2626
const (
2727
clusterInfo contextKey = "clusterInfo"
2828

29-
ClusterLabelKey = "kusionstack.io/cluster"
29+
ClusterLabelKey = "kusionstack.io/cluster" // Label key for cluster name that will be attached when use Client or Cache to read
3030

31-
EnvClusterAllowList = "CLUSTER_ALLOW_LIST"
31+
EnvClusterAllowList = "CLUSTER_ALLOW_LIST" // Comma separated list of cluster names that are allowed to be accessed
32+
EnvClusterBlockList = "CLUSTER_BLOCK_LIST" // Comma separated list of cluster names that are blocked to be accessed
3233
)
3334

3435
const (

multicluster/controller/controller.go

Lines changed: 87 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/go-logr/logr"
2626
"k8s.io/apimachinery/pkg/api/errors"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2829
"k8s.io/apimachinery/pkg/runtime/schema"
2930
"k8s.io/apimachinery/pkg/util/runtime"
3031
"k8s.io/apimachinery/pkg/util/wait"
@@ -37,94 +38,80 @@ import (
3738
"kusionstack.io/kube-utils/multicluster/metrics"
3839
)
3940

40-
type ClusterManagementType string
41-
42-
const (
43-
OpenClusterManagement ClusterManagementType = "OpenClusterManagement"
44-
TestCluterManagement ClusterManagementType = "TestCluterManagement"
45-
)
41+
type ClusterProvider interface {
42+
Init(config *rest.Config) // Init is used to initialize the cluster provider, config is the kubeconfig for the fed cluster
43+
GetClusterMangementGVR() schema.GroupVersionResource // The GVR will be used to watch cluster management resource
44+
GetClusterName(obj *unstructured.Unstructured) string // Get cluster name from cluster management resource, cluster name is used to identify the cluster
45+
GetClusterConfig(obj *unstructured.Unstructured) *rest.Config // Get kubeconfig from cluster management resource
46+
}
4647

4748
type Controller struct {
48-
config *rest.Config
49-
clusterManagementType ClusterManagementType
50-
clusterManagementGVR schema.GroupVersionResource
49+
config *rest.Config
50+
clusterProvider ClusterProvider
5151

52-
client dynamic.Interface // client to get cluster info
52+
client dynamic.Interface // Client to get cluster info
5353
informerFactory dynamicinformer.DynamicSharedInformerFactory
5454
informer cache.SharedIndexInformer
5555
workqueue workqueue.RateLimitingInterface
5656

57-
mutex sync.Mutex
58-
syncedNum int // number of synced cluster
59-
syncedCh chan struct{}
57+
mutex sync.RWMutex
58+
syncedNum int // Number of synced cluster
59+
syncedCh chan struct{} // Channel to notify all synced clusters have been processed
6060

61-
addUpdateHandler func(string) error // when cluster is added or updated, this handler will be called
62-
deleteHandler func(string) // when cluster is deleted, this handler will be called
63-
log logr.Logger
61+
addUpdateHandler func(string) error // When cluster is added or updated, this handler will be invoked
62+
deleteHandler func(string) // When cluster is deleted, this handler will be invoked
6463

65-
// for test
66-
restConfigForCluster func(cluster string) *rest.Config
64+
clusterNameToNamespacedKey map[string]string
65+
namespacedKeyToObj map[string]*unstructured.Unstructured
66+
log logr.Logger
6767
}
6868

6969
type ControllerConfig struct {
70-
Config *rest.Config // config for cluster management
71-
ClusterManagementType ClusterManagementType
72-
ClusterManagementGVR *schema.GroupVersionResource
73-
ResyncPeriod time.Duration // resync period for cluster management
74-
Log logr.Logger
75-
76-
// for test
77-
RestConfigForCluster func(cluster string) *rest.Config
70+
Config *rest.Config // Kubeconfig for the fed cluster
71+
ClusterProvider ClusterProvider
72+
ResyncPeriod time.Duration // Resync period for cluster management
73+
Log logr.Logger
7874
}
7975

8076
// NewController creates a new Controller which will process events about cluster.
8177
func NewController(cfg *ControllerConfig) (*Controller, error) {
82-
var clusterManagementGVR schema.GroupVersionResource
83-
switch cfg.ClusterManagementType {
84-
case OpenClusterManagement:
85-
if cfg.ClusterManagementGVR == nil {
86-
return nil, fmt.Errorf("ClusterManagementGVR must be set when use %s", cfg.ClusterManagementType)
87-
}
88-
clusterManagementGVR = *cfg.ClusterManagementGVR
89-
case TestCluterManagement:
90-
if cfg.ClusterManagementGVR == nil || cfg.RestConfigForCluster == nil {
91-
return nil, fmt.Errorf("ClusterManagementGVR and RestConfigForCluster must be set when use %s", cfg.ClusterManagementType)
92-
}
93-
clusterManagementGVR = *cfg.ClusterManagementGVR
94-
default:
95-
return nil, fmt.Errorf("not support cluster management type: %v", cfg.ClusterManagementType)
96-
}
97-
9878
client, err := dynamic.NewForConfig(cfg.Config)
9979
if err != nil {
10080
return nil, err
10181
}
82+
if cfg.ClusterProvider == nil {
83+
return nil, fmt.Errorf("ClusterProvider is required")
84+
}
85+
10286
informerFactory := dynamicinformer.NewDynamicSharedInformerFactory(client, cfg.ResyncPeriod)
103-
informer := informerFactory.ForResource(clusterManagementGVR).Informer()
87+
informer := informerFactory.ForResource(cfg.ClusterProvider.GetClusterMangementGVR()).Informer()
10488

10589
return &Controller{
106-
config: cfg.Config,
107-
client: client,
108-
informerFactory: informerFactory,
109-
clusterManagementType: cfg.ClusterManagementType,
110-
clusterManagementGVR: clusterManagementGVR,
111-
informer: informer,
112-
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), clusterManagementGVR.Resource),
113-
syncedCh: make(chan struct{}),
114-
log: cfg.Log,
115-
116-
restConfigForCluster: cfg.RestConfigForCluster,
90+
config: cfg.Config,
91+
clusterProvider: cfg.ClusterProvider,
92+
93+
client: client,
94+
informerFactory: informerFactory,
95+
informer: informer,
96+
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), cfg.ClusterProvider.GetClusterMangementGVR().Resource),
97+
syncedCh: make(chan struct{}),
98+
99+
clusterNameToNamespacedKey: make(map[string]string), // Get namespaced key by cluster name
100+
namespacedKeyToObj: make(map[string]*unstructured.Unstructured), // Get cluster management resource by namespaced key
101+
log: cfg.Log,
117102
}, nil
118103
}
119104

120-
// AddEventHandler adds handler for events about cluster.
105+
// AddEventHandler adds handlers which will be invoked.
106+
// When cluster is added or updated, addUpdateHandler will be invoked.
107+
// When cluster is deleted, deleteHandler will be invoked.
121108
func (c *Controller) AddEventHandler(addUpdateHandler func(string) error, deleteHandler func(string)) {
122109
c.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
123-
AddFunc: c.enqueueClusterExtension,
110+
AddFunc: c.enqueueClusterEvent,
124111
UpdateFunc: func(old, new interface{}) {
125-
c.enqueueClusterExtension(new)
112+
c.enqueueClusterEvent(new)
126113
},
127-
DeleteFunc: c.enqueueClusterExtension,
114+
DeleteFunc: c.enqueueClusterEvent,
128115
})
129116

130117
c.addUpdateHandler = addUpdateHandler
@@ -135,6 +122,8 @@ func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {
135122
defer runtime.HandleCrash()
136123
defer c.workqueue.ShutDown()
137124

125+
c.clusterProvider.Init(c.config)
126+
138127
c.informerFactory.Start(stopCh)
139128

140129
// Wait for the caches to be synced before starting workers
@@ -164,10 +153,9 @@ func (c *Controller) WaitForSynced(ctx context.Context) bool {
164153
}
165154
}
166155

167-
func (c *Controller) enqueueClusterExtension(obj interface{}) {
168-
var key string
169-
var err error
170-
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
156+
func (c *Controller) enqueueClusterEvent(obj interface{}) {
157+
key, err := cache.MetaNamespaceKeyFunc(obj)
158+
if err != nil {
171159
c.log.Error(err, "failed to get enqueue key")
172160
return
173161
}
@@ -205,11 +193,9 @@ func (c *Controller) processNextWorkItem() bool {
205193

206194
if err := c.eventHandler(key); err != nil {
207195
c.workqueue.AddRateLimited(key)
208-
metrics.NewControllerEventCountMetrics(key, "failed").Inc()
209196
return err
210197
}
211198

212-
metrics.NewControllerEventCountMetrics(key, "ok").Inc()
213199
c.workqueue.Forget(obj)
214200
return nil
215201
}(obj)
@@ -228,35 +214,60 @@ func (c *Controller) eventHandler(key string) error {
228214
return nil
229215
}
230216

231-
_, err = c.client.Resource(c.clusterManagementGVR).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
217+
obj, err := c.client.Resource(c.clusterProvider.GetClusterMangementGVR()).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
232218
if err != nil {
233219
if errors.IsNotFound(err) {
234-
c.deleteHandler(name)
220+
c.mutex.Lock()
221+
defer c.mutex.Unlock()
222+
223+
oldObj, ok := c.namespacedKeyToObj[key]
224+
if !ok {
225+
return nil
226+
}
227+
delete(c.namespacedKeyToObj, key)
228+
229+
clusterName := c.clusterProvider.GetClusterName(oldObj)
230+
delete(c.clusterNameToNamespacedKey, clusterName)
231+
232+
metrics.NewClusterEventCountMetrics(key, "delete", "true").Inc()
233+
c.deleteHandler(clusterName)
235234
return nil
236235
}
236+
metrics.NewClusterEventCountMetrics(key, "delete", "false").Inc()
237237
c.log.Error(err, "failed to get resource", "key", key)
238238
return err
239239
}
240240

241-
err = c.addUpdateHandler(name)
241+
c.mutex.Lock()
242+
c.namespacedKeyToObj[key] = obj
243+
clusterName := c.clusterProvider.GetClusterName(obj)
244+
c.clusterNameToNamespacedKey[clusterName] = key
245+
c.mutex.Unlock()
246+
247+
err = c.addUpdateHandler(clusterName)
242248
if err != nil {
249+
metrics.NewClusterEventCountMetrics(key, "add-update", "false").Inc()
243250
c.log.Error(err, "failed to add or update cluster", "key", key)
244251
return err
245252
}
246253

254+
metrics.NewClusterEventCountMetrics(key, "add-update", "true").Inc()
247255
return nil
248256
}
249257

250258
// RestConfigForCluster returns the rest config for the mangered cluster.
251-
func (c *Controller) RestConfigForCluster(cluster string) *rest.Config {
252-
switch c.clusterManagementType {
253-
case OpenClusterManagement:
254-
clusterConfig := *c.config
255-
clusterConfig.Host = fmt.Sprintf("%s/apis/%s/%s/%s/%s/proxy", clusterConfig.Host, c.clusterManagementGVR.Group, c.clusterManagementGVR.Version, c.clusterManagementGVR.Resource, cluster)
256-
return &clusterConfig
257-
case TestCluterManagement:
258-
return c.restConfigForCluster(cluster)
259-
default:
259+
func (c *Controller) RestConfigForCluster(clusterName string) *rest.Config {
260+
c.mutex.RLock()
261+
defer c.mutex.RUnlock()
262+
263+
namespacedKey, ok := c.clusterNameToNamespacedKey[clusterName]
264+
if !ok {
265+
return nil
266+
}
267+
268+
obj, ok := c.namespacedKeyToObj[namespacedKey]
269+
if !ok {
260270
return nil
261271
}
272+
return c.clusterProvider.GetClusterConfig(obj)
262273
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Copyright 2024 KusionStack Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package controller
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
21+
"k8s.io/apimachinery/pkg/runtime/schema"
22+
"k8s.io/client-go/rest"
23+
)
24+
25+
var _ ClusterProvider = &TestClusterProvider{}
26+
27+
type TestClusterProvider struct {
28+
schema.GroupVersionResource
29+
ClusterNameToConfig map[string]*rest.Config // Map from cluster name to kubeconfig
30+
}
31+
32+
func (p *TestClusterProvider) Init(config *rest.Config) {
33+
// Do nothing
34+
}
35+
36+
func (p *TestClusterProvider) GetClusterMangementGVR() schema.GroupVersionResource {
37+
return p.GroupVersionResource
38+
}
39+
40+
func (p *TestClusterProvider) GetClusterName(obj *unstructured.Unstructured) string {
41+
if obj == nil {
42+
return ""
43+
}
44+
return obj.GetName() // Use resource name as cluster name
45+
}
46+
47+
func (p *TestClusterProvider) GetClusterConfig(obj *unstructured.Unstructured) *rest.Config {
48+
if obj == nil || p.ClusterNameToConfig == nil {
49+
return nil
50+
}
51+
config, ok := p.ClusterNameToConfig[obj.GetName()]
52+
if !ok {
53+
return nil
54+
}
55+
return config
56+
}

0 commit comments

Comments
 (0)