mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-08-23 22:26:49 +00:00
202 lines
8.0 KiB
Go
202 lines
8.0 KiB
Go
package managedcluster
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/errors"
|
|
"k8s.io/client-go/discovery"
|
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
kevents "k8s.io/client-go/tools/events"
|
|
aboutv1alpha1informer "sigs.k8s.io/about-api/pkg/generated/informers/externalversions/apis/v1alpha1"
|
|
|
|
clientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
|
|
clusterv1informer "open-cluster-management.io/api/client/cluster/informers/externalversions/cluster/v1"
|
|
clusterv1alpha1informer "open-cluster-management.io/api/client/cluster/informers/externalversions/cluster/v1alpha1"
|
|
clusterv1listers "open-cluster-management.io/api/client/cluster/listers/cluster/v1"
|
|
clusterv1 "open-cluster-management.io/api/cluster/v1"
|
|
ocmfeature "open-cluster-management.io/api/feature"
|
|
"open-cluster-management.io/sdk-go/pkg/basecontroller/factory"
|
|
"open-cluster-management.io/sdk-go/pkg/patcher"
|
|
|
|
"open-cluster-management.io/ocm/pkg/features"
|
|
)
|
|
|
|
// managedClusterStatusController checks the kube-apiserver health on managed cluster to determine it whether is available
|
|
// and ensure that the managed cluster resources and version are up to date.
|
|
type managedClusterStatusController struct {
|
|
clusterName string
|
|
reconcilers []statusReconcile
|
|
patcher patcher.Patcher[*clusterv1.ManagedCluster, clusterv1.ManagedClusterSpec, clusterv1.ManagedClusterStatus]
|
|
hubClusterLister clusterv1listers.ManagedClusterLister
|
|
hubEventRecorder kevents.EventRecorder
|
|
}
|
|
|
|
type statusReconcile interface {
|
|
reconcile(ctx context.Context, syncCtx factory.SyncContext, cm *clusterv1.ManagedCluster) (*clusterv1.ManagedCluster, reconcileState, error)
|
|
}
|
|
|
|
type reconcileState int64
|
|
|
|
const (
|
|
reconcileStop reconcileState = iota
|
|
reconcileContinue
|
|
)
|
|
|
|
// NewManagedClusterStatusController creates a managed cluster status controller on managed cluster.
|
|
func NewManagedClusterStatusController(
|
|
clusterName string,
|
|
hubHash string,
|
|
hubClusterClient clientset.Interface,
|
|
spokeKubeClient kubernetes.Interface,
|
|
hubClusterInformer clusterv1informer.ManagedClusterInformer,
|
|
spokeNamespaceInformer corev1informers.NamespaceInformer,
|
|
managedClusterDiscoveryClient discovery.DiscoveryInterface,
|
|
claimInformer clusterv1alpha1informer.ClusterClaimInformer,
|
|
propertyInformer aboutv1alpha1informer.ClusterPropertyInformer,
|
|
nodeInformer corev1informers.NodeInformer,
|
|
maxCustomClusterClaims int,
|
|
reservedClusterClaimSuffixes []string,
|
|
resyncInterval time.Duration,
|
|
hubEventRecorder kevents.EventRecorder) factory.Controller {
|
|
c := newManagedClusterStatusController(
|
|
clusterName,
|
|
hubHash,
|
|
hubClusterClient,
|
|
spokeKubeClient,
|
|
hubClusterInformer,
|
|
spokeNamespaceInformer,
|
|
managedClusterDiscoveryClient,
|
|
claimInformer,
|
|
propertyInformer,
|
|
nodeInformer,
|
|
maxCustomClusterClaims,
|
|
reservedClusterClaimSuffixes,
|
|
hubEventRecorder,
|
|
)
|
|
|
|
controllerFactory := factory.New().
|
|
WithInformers(hubClusterInformer.Informer(), nodeInformer.Informer(), spokeNamespaceInformer.Informer()).
|
|
WithSync(c.sync).ResyncEvery(resyncInterval)
|
|
|
|
if features.SpokeMutableFeatureGate.Enabled(ocmfeature.ClusterClaim) {
|
|
controllerFactory = controllerFactory.WithInformers(claimInformer.Informer())
|
|
}
|
|
if features.SpokeMutableFeatureGate.Enabled(ocmfeature.ClusterProperty) {
|
|
controllerFactory = controllerFactory.WithInformers(propertyInformer.Informer())
|
|
}
|
|
|
|
return controllerFactory.ToController("ManagedClusterStatusController")
|
|
}
|
|
|
|
func newManagedClusterStatusController(
|
|
clusterName string,
|
|
hubHash string,
|
|
hubClusterClient clientset.Interface,
|
|
spokeKubeClient kubernetes.Interface,
|
|
hubClusterInformer clusterv1informer.ManagedClusterInformer,
|
|
spokeNamespaceInformer corev1informers.NamespaceInformer,
|
|
managedClusterDiscoveryClient discovery.DiscoveryInterface,
|
|
claimInformer clusterv1alpha1informer.ClusterClaimInformer,
|
|
propertyInformer aboutv1alpha1informer.ClusterPropertyInformer,
|
|
nodeInformer corev1informers.NodeInformer,
|
|
maxCustomClusterClaims int,
|
|
reservedClusterClaimSuffixes []string,
|
|
hubEventRecorder kevents.EventRecorder) *managedClusterStatusController {
|
|
return &managedClusterStatusController{
|
|
clusterName: clusterName,
|
|
patcher: patcher.NewPatcher[
|
|
*clusterv1.ManagedCluster, clusterv1.ManagedClusterSpec, clusterv1.ManagedClusterStatus](
|
|
hubClusterClient.ClusterV1().ManagedClusters()),
|
|
reconcilers: []statusReconcile{
|
|
&joiningReconcile{},
|
|
// availableReconcile must run before resource and claim reconcilers because it checks
|
|
// kube-apiserver health. If the API server is unavailable, it returns reconcileStop to
|
|
// skip resource/claim gathering which would fail against an unreachable API server.
|
|
&availableReconcile{managedClusterDiscoveryClient: managedClusterDiscoveryClient},
|
|
&resoureReconcile{managedClusterDiscoveryClient: managedClusterDiscoveryClient, nodeLister: nodeInformer.Lister()},
|
|
&claimReconcile{claimLister: claimInformer.Lister(),
|
|
maxCustomClusterClaims: maxCustomClusterClaims,
|
|
reservedClusterClaimSuffixes: reservedClusterClaimSuffixes,
|
|
aboutLister: propertyInformer.Lister(),
|
|
},
|
|
&managedNamespaceReconcile{
|
|
hubClusterSetLabel: GetHubClusterSetLabel(hubHash),
|
|
spokeKubeClient: spokeKubeClient,
|
|
spokeNamespaceLister: spokeNamespaceInformer.Lister(),
|
|
},
|
|
},
|
|
hubClusterLister: hubClusterInformer.Lister(),
|
|
hubEventRecorder: hubEventRecorder,
|
|
}
|
|
}
|
|
|
|
// sync updates managed cluster available condition by checking kube-apiserver health on managed cluster.
|
|
// if the kube-apiserver is health, it will ensure that managed cluster resources and version are up to date.
|
|
func (c *managedClusterStatusController) sync(ctx context.Context, syncCtx factory.SyncContext, _ string) error {
|
|
cluster, err := c.hubClusterLister.Get(c.clusterName)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get managed cluster %q from hub: %w", c.clusterName, err)
|
|
}
|
|
|
|
newCluster := cluster.DeepCopy()
|
|
var errs []error
|
|
for _, reconciler := range c.reconcilers {
|
|
var state reconcileState
|
|
newCluster, state, err = reconciler.reconcile(ctx, syncCtx, newCluster)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
if state == reconcileStop {
|
|
break
|
|
}
|
|
}
|
|
|
|
// check if managedcluster's clock is out of sync, if so, the agent will not be able to update the status of managed cluster.
|
|
outOfSynced := meta.IsStatusConditionFalse(newCluster.Status.Conditions, clusterv1.ManagedClusterConditionClockSynced)
|
|
if outOfSynced {
|
|
syncCtx.Recorder().Eventf(ctx, "ClockOutOfSync", "The managed cluster's clock is out of sync, the agent will not be able to update the status of managed cluster.")
|
|
return fmt.Errorf("the managed cluster's clock is out of sync, the agent will not be able to update the status of managed cluster")
|
|
}
|
|
|
|
changed, err := c.patcher.PatchStatus(ctx, newCluster, newCluster.Status, cluster.Status)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
if changed {
|
|
c.sendAvailableConditionEvent(cluster, newCluster)
|
|
}
|
|
return errors.NewAggregate(errs)
|
|
}
|
|
|
|
func (c *managedClusterStatusController) sendAvailableConditionEvent(
|
|
cluster, newCluster *clusterv1.ManagedCluster) {
|
|
condition := meta.FindStatusCondition(cluster.Status.Conditions, clusterv1.ManagedClusterConditionAvailable)
|
|
newCondition := meta.FindStatusCondition(newCluster.Status.Conditions, clusterv1.ManagedClusterConditionAvailable)
|
|
if newCondition == nil {
|
|
return
|
|
}
|
|
if condition != nil && condition.Status == newCondition.Status {
|
|
|
|
return
|
|
}
|
|
|
|
// send event to hub cluster in cluster namespace
|
|
newCluster.SetNamespace(newCluster.Name)
|
|
switch newCondition.Status {
|
|
case metav1.ConditionTrue:
|
|
c.hubEventRecorder.Eventf(newCluster, nil, corev1.EventTypeNormal, "Available", "Available",
|
|
"The %s is successfully imported, and it is managed by the hub cluster. Its apieserver is available",
|
|
cluster.Name)
|
|
|
|
case metav1.ConditionFalse:
|
|
c.hubEventRecorder.Eventf(newCluster, nil, corev1.EventTypeWarning, "Unavailable", "Unavailable",
|
|
"The %s is successfully imported. However, its Kube API server is unavailable", cluster.Name)
|
|
}
|
|
}
|