mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-05-10 11:19:11 +00:00
216 lines
7.6 KiB
Go
216 lines
7.6 KiB
Go
package managedclusterset
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
|
|
clientset "github.com/open-cluster-management/api/client/cluster/clientset/versioned"
|
|
clusterinformerv1 "github.com/open-cluster-management/api/client/cluster/informers/externalversions/cluster/v1"
|
|
clusterinformerv1alpha1 "github.com/open-cluster-management/api/client/cluster/informers/externalversions/cluster/v1alpha1"
|
|
clusterlisterv1 "github.com/open-cluster-management/api/client/cluster/listers/cluster/v1"
|
|
clusterlisterv1alpha1 "github.com/open-cluster-management/api/client/cluster/listers/cluster/v1alpha1"
|
|
clusterv1 "github.com/open-cluster-management/api/cluster/v1"
|
|
clusterv1alpha1 "github.com/open-cluster-management/api/cluster/v1alpha1"
|
|
"github.com/openshift/library-go/pkg/controller/factory"
|
|
"github.com/openshift/library-go/pkg/operator/events"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
const (
|
|
clusterSetLabel = "cluster.open-cluster-management.io/clusterset"
|
|
)
|
|
|
|
// managedClusterSetController reconciles instances of ManagedClusterSet on the hub.
|
|
type managedClusterSetController struct {
|
|
clusterClient clientset.Interface
|
|
clusterLister clusterlisterv1.ManagedClusterLister
|
|
clusterSetLister clusterlisterv1alpha1.ManagedClusterSetLister
|
|
eventRecorder events.Recorder
|
|
|
|
// clusterSetsMap caches the mappings between clusters and clustersets.
|
|
// With the mappings, it's easy to find out which clustersets are impacted on a
|
|
// change of cluster.
|
|
clusterSetsMap map[string]string
|
|
|
|
// mapLock protects the read/write of clusterSetsMap
|
|
mapLock sync.RWMutex
|
|
}
|
|
|
|
// NewManagedClusterSetController creates a new managed cluster set controller
|
|
func NewManagedClusterSetController(
|
|
clusterClient clientset.Interface,
|
|
clusterInformer clusterinformerv1.ManagedClusterInformer,
|
|
clusterSetInformer clusterinformerv1alpha1.ManagedClusterSetInformer,
|
|
recorder events.Recorder) factory.Controller {
|
|
c := &managedClusterSetController{
|
|
clusterClient: clusterClient,
|
|
clusterLister: clusterInformer.Lister(),
|
|
clusterSetLister: clusterSetInformer.Lister(),
|
|
eventRecorder: recorder.WithComponentSuffix("managed-cluster-set-controller"),
|
|
|
|
clusterSetsMap: map[string]string{},
|
|
}
|
|
return factory.New().
|
|
WithInformersQueueKeyFunc(func(obj runtime.Object) string {
|
|
accessor, _ := meta.Accessor(obj)
|
|
return accessor.GetName()
|
|
}, clusterSetInformer.Informer()).
|
|
// register two event handlers to cluster informer. One haneles the current parent
|
|
// clusterset, and the other handles the original parent clusterset. The order of
|
|
// the registering matters. originalClusterSetQueueKeyFunc must be called before
|
|
// currentClusterSetQueueKeyFunc is called, otherwise clusterSetsMap will be updated
|
|
// unexpectedly
|
|
//
|
|
// TODO, create a PR for library-go to accept a queue key function, which
|
|
// is able to produce a []string instead of string based on one object, when
|
|
// registering event handler. And then refactor the logic here.
|
|
WithInformersQueueKeyFunc(c.currentClusterSetQueueKeyFunc, clusterInformer.Informer()).
|
|
WithInformersQueueKeyFunc(c.originalClusterSetQueueKeyFunc, clusterInformer.Informer()).
|
|
WithSync(c.sync).
|
|
ToController("ManagedClusterSetController", recorder)
|
|
}
|
|
|
|
// originalClusterSetQueueKeyFunc returns the original clusterset the cluster previously
|
|
// belonged to.
|
|
func (c *managedClusterSetController) originalClusterSetQueueKeyFunc(obj runtime.Object) string {
|
|
c.mapLock.RLock()
|
|
defer c.mapLock.RUnlock()
|
|
|
|
accessor, _ := meta.Accessor(obj)
|
|
clusterName := accessor.GetName()
|
|
originalClusterSetName := c.clusterSetsMap[clusterName]
|
|
|
|
var currentClusterSetName string
|
|
if clusterLabels := accessor.GetLabels(); clusterLabels != nil {
|
|
currentClusterSetName = clusterLabels[clusterSetLabel]
|
|
}
|
|
|
|
// return the name of clusterset it previously belonged to only if the parent clusterset
|
|
// is changed
|
|
if originalClusterSetName != currentClusterSetName {
|
|
return originalClusterSetName
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// currentClusterSetQueueKeyFunc returns the current clusterset the cluster currently
|
|
// belongs to.
|
|
func (c *managedClusterSetController) currentClusterSetQueueKeyFunc(obj runtime.Object) string {
|
|
accessor, _ := meta.Accessor(obj)
|
|
|
|
// always return the name of clusterset it currently belongs to
|
|
if clusterLabels := accessor.GetLabels(); clusterLabels != nil {
|
|
return clusterLabels[clusterSetLabel]
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (c *managedClusterSetController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
|
|
clusterSetName := syncCtx.QueueKey()
|
|
if len(clusterSetName) == 0 {
|
|
return nil
|
|
}
|
|
klog.Infof("Reconciling ManagedClusterSet %s", clusterSetName)
|
|
|
|
clusterSet, err := c.clusterSetLister.Get(clusterSetName)
|
|
if errors.IsNotFound(err) {
|
|
// cluster set not found, could have been deleted, do nothing.
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// no work to do if the cluster set is deleted
|
|
if !clusterSet.DeletionTimestamp.IsZero() {
|
|
return nil
|
|
}
|
|
|
|
if err := c.syncClusterSet(ctx, clusterSet); err != nil {
|
|
return fmt.Errorf("failed to sync ManagedClusterSet %q: %w", clusterSetName, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// syncClusterSet syncs a particular cluster set
|
|
func (c *managedClusterSetController) syncClusterSet(ctx context.Context, originalClusterSet *clusterv1alpha1.ManagedClusterSet) error {
|
|
clusterSet := originalClusterSet.DeepCopy()
|
|
|
|
// find out the containing clusters of clusterset
|
|
selector := labels.SelectorFromSet(labels.Set{
|
|
clusterSetLabel: clusterSet.Name,
|
|
})
|
|
clusters, err := c.clusterLister.List(selector)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list ManagedClusters: %w", err)
|
|
}
|
|
|
|
//update the cluster-to-clusterset mappings
|
|
c.updateClusterSetsMap(clusterSet.Name, clusters)
|
|
|
|
// update clusterset status
|
|
emptyCondition := metav1.Condition{
|
|
Type: clusterv1alpha1.ManagedClusterSetConditionEmpty,
|
|
}
|
|
if count := len(clusters); count == 0 {
|
|
emptyCondition.Status = metav1.ConditionTrue
|
|
emptyCondition.Reason = "NoClusterMatched"
|
|
emptyCondition.Message = "No ManagedCluster selected"
|
|
} else {
|
|
emptyCondition.Status = metav1.ConditionFalse
|
|
emptyCondition.Reason = "ClustersSelected"
|
|
emptyCondition.Message = fmt.Sprintf("%d ManagedClusters selected", count)
|
|
}
|
|
meta.SetStatusCondition(&clusterSet.Status.Conditions, emptyCondition)
|
|
|
|
// skip update if cluster set status does not change
|
|
if reflect.DeepEqual(clusterSet.Status.Conditions, originalClusterSet.Status.Conditions) {
|
|
return nil
|
|
}
|
|
|
|
_, err = c.clusterClient.ClusterV1alpha1().ManagedClusterSets().UpdateStatus(ctx, clusterSet, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update status of ManagedClusterSet %q: %w", clusterSet.Name, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateClusterSetsMap updates the cluster-to-clusterset mappings with memebers of a
|
|
// given cluster set
|
|
func (c *managedClusterSetController) updateClusterSetsMap(clusterSetName string, clusters []*clusterv1.ManagedCluster) {
|
|
c.mapLock.Lock()
|
|
defer c.mapLock.Unlock()
|
|
|
|
newMembers := sets.NewString()
|
|
for _, cluster := range clusters {
|
|
newMembers.Insert(cluster.Name)
|
|
}
|
|
|
|
originalMembers := sets.NewString()
|
|
for c, cs := range c.clusterSetsMap {
|
|
if cs != clusterSetName {
|
|
continue
|
|
}
|
|
originalMembers.Insert(c)
|
|
}
|
|
|
|
toBeAdded := newMembers.Difference(originalMembers)
|
|
toBeRemoved := originalMembers.Difference(newMembers)
|
|
for clusterName := range toBeAdded {
|
|
c.clusterSetsMap[clusterName] = clusterSetName
|
|
}
|
|
for clusterName := range toBeRemoved {
|
|
delete(c.clusterSetsMap, clusterName)
|
|
}
|
|
}
|