* Copyright (c) 2025 Bocloud Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain n copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
******************************************************************/
package phaseframe
import (
"context"
"sync"
"time"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/controller-runtime/pkg/client"
confv1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/bkecommon/v1beta1"
bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/capbke/v1beta1"
bkenode "gopkg.openfuyao.cn/cluster-api-provider-bke/common/cluster/node"
"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/mergecluster"
releasemanifest "gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/release/manifest"
"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/upgrade"
"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/capbke/annotation"
"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/capbke/constant"
"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/capbke/nodeutil"
"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
)
type PhaseContext struct {
BKECluster *bkev1beta1.BKECluster
Cluster *clusterv1.Cluster
client.Client
context.Context
Log *bkev1beta1.BKELogger
Scheme *runtime.Scheme
RestConfig *rest.Config
cancelFunc context.CancelFunc
mux sync.RWMutex
nodeFetcher *nodeutil.NodeFetcher
VersionContext *upgrade.VersionContext
DeclarativeDAGCompleted bool
}
func NewReconcilePhaseCtx(ctx context.Context) *PhaseContext {
phaseCancelCtx, phaseCancel := context.WithCancel(ctx)
return &PhaseContext{
Context: phaseCancelCtx,
cancelFunc: phaseCancel,
mux: sync.RWMutex{},
}
}
func (pc *PhaseContext) SetBKECluster(bkeCluster *bkev1beta1.BKECluster) *PhaseContext {
pc.mux.Lock()
pc.BKECluster = bkeCluster
pc.mux.Unlock()
return pc
}
func (pc *PhaseContext) SetCluster(cluster *clusterv1.Cluster) *PhaseContext {
pc.mux.Lock()
pc.Cluster = cluster
pc.mux.Unlock()
return pc
}
func (pc *PhaseContext) SetClient(client client.Client) *PhaseContext {
pc.Client = client
return pc
}
func (pc *PhaseContext) SetLogger(log *bkev1beta1.BKELogger) *PhaseContext {
pc.Log = log
return pc
}
func (pc *PhaseContext) BindPhaseLogger(phaseName confv1beta1.BKEClusterPhase) {
if pc == nil || pc.Log == nil {
return
}
pc.Log.SetNormalLogger(
log.With("phase", phaseName.String()).
With("bkecluster", utils.ClientObjNS(pc.BKECluster)),
)
}
func (pc *PhaseContext) SetScheme(scheme *runtime.Scheme) *PhaseContext {
pc.Scheme = scheme
return pc
}
func (pc *PhaseContext) SetRestConfig(restConfig *rest.Config) *PhaseContext {
pc.RestConfig = restConfig
return pc
}
func (pc *PhaseContext) SetVersionContext(vc *upgrade.VersionContext) *PhaseContext {
pc.VersionContext = vc
return pc
}
func (pc *PhaseContext) BuildAndSetVersionContext() *PhaseContext {
if pc.VersionContext != nil {
return pc
}
if pc.BKECluster == nil {
return pc
}
pc.VersionContext = upgrade.BuildVersionContextFromBKECluster(pc.BKECluster)
return pc
}
func (pc *PhaseContext) BuildAndSetVersionContextFromBundle(
targetBundle, currentBundle *releasemanifest.Bundle,
) *PhaseContext {
pc.VersionContext = upgrade.BuildVersionContextForUpgrade(targetBundle, currentBundle, pc.BKECluster)
return pc
}
func (pc *PhaseContext) FinishDeclarativeDAGForPhaseFlow() error {
if pc == nil {
return nil
}
pc.VersionContext = nil
pc.DeclarativeDAGCompleted = true
return pc.RefreshCtxBKECluster()
}
func (pc *PhaseContext) Untie() (context.Context, client.Client, *bkev1beta1.BKECluster, *runtime.Scheme, *bkev1beta1.BKELogger) {
pc.mux.RLock()
defer pc.mux.RUnlock()
return pc.Context, pc.Client, pc.BKECluster, pc.Scheme, pc.Log
}
func (pc *PhaseContext) fetchNewestBKECluster(ctx context.Context, bkeCluster *bkev1beta1.BKECluster) (*bkev1beta1.BKECluster, error) {
if bkeCluster == nil {
return nil, errors.New("BKECluster is nil")
}
return mergecluster.GetCombinedBKECluster(ctx, pc.Client, bkeCluster.Namespace, bkeCluster.Name)
}
func (pc *PhaseContext) GetNewestBKECluster(customCtx ...context.Context) (*bkev1beta1.BKECluster, error) {
var getCtx context.Context
pc.mux.RLock()
if customCtx != nil && len(customCtx) != 0 {
getCtx = customCtx[0]
} else {
getCtx = pc.Context
}
bkeCluster := pc.BKECluster
pc.mux.RUnlock()
return pc.fetchNewestBKECluster(getCtx, bkeCluster)
}
func (pc *PhaseContext) RefreshCtxBKECluster(customCtx ...context.Context) error {
pc.mux.Lock()
defer pc.mux.Unlock()
var ctx context.Context
if customCtx != nil && len(customCtx) != 0 {
ctx = customCtx[0]
} else {
ctx = pc.Context
}
newBKECluster, err := pc.fetchNewestBKECluster(ctx, pc.BKECluster)
if err != nil {
log.Errorf("failed to get newest BKECluster: %v", err)
return err
}
pc.BKECluster = newBKECluster
return nil
}
func (pc *PhaseContext) RefreshCtxCluster(customCtx ...context.Context) error {
var refreshCtx context.Context
if customCtx != nil && len(customCtx) != 0 {
refreshCtx = customCtx[0]
} else {
refreshCtx = pc.Context
}
err := pc.RefreshCtxBKECluster(refreshCtx)
if err != nil {
log.Errorf("failed to refresh BKECluster in RefreshCtxCluster: %v", err)
return err
}
pc.mux.RLock()
bkeCluster := pc.BKECluster
pc.mux.RUnlock()
cluster, err := util.GetOwnerCluster(refreshCtx, pc.Client, bkeCluster.ObjectMeta)
if err != nil {
return errors.Wrapf(err, "failed to get owner cluster")
}
if cluster == nil {
return errors.New("owner cluster is nil")
}
pc.mux.Lock()
pc.Cluster = cluster
pc.mux.Unlock()
return nil
}
func (pc *PhaseContext) Cancel() {
pc.cancelFunc()
}
func (pc *PhaseContext) WatchBKEClusterStatus() {
refreshTicker := time.NewTicker(2 * time.Second)
defer refreshTicker.Stop()
pausedTicker := time.NewTicker(10 * time.Second)
defer pausedTicker.Stop()
pc.mux.RLock()
if pc.BKECluster == nil {
pc.mux.RUnlock()
if pc.Log != nil {
pc.Log.Error("", "BKECluster is nil, cannot watch status")
}
return
}
pc.mux.RUnlock()
bkeCluster, err := pc.GetNewestBKECluster()
if err != nil {
if pc.Log != nil {
pc.Log.Warn(constant.ReconcileErrorReason, "failed to get newest BKECluster: %v", err)
}
return
}
select {
case <-refreshTicker.C:
cluster, ok := pc.refreshWatchedCluster()
if !ok {
return
}
bkeCluster = cluster
case <-pausedTicker.C:
bkeCluster = pc.currentBKEClusterOrDefault(bkeCluster)
pc.logPausedRunningPhase(bkeCluster)
case <-pc.Done():
return
default:
bkeCluster = pc.currentBKEClusterOrDefault(bkeCluster)
if pc.handleDeletingBKECluster(bkeCluster) {
return
}
}
}
func (pc *PhaseContext) refreshWatchedCluster() (*bkev1beta1.BKECluster, bool) {
cluster, err := pc.GetNewestBKECluster()
if err != nil {
return nil, false
}
return cluster, true
}
func (pc *PhaseContext) currentBKEClusterOrDefault(defaultCluster *bkev1beta1.BKECluster) *bkev1beta1.BKECluster {
pc.mux.RLock()
current := pc.BKECluster
pc.mux.RUnlock()
if current != nil {
return current
}
return defaultCluster
}
func (pc *PhaseContext) logPausedRunningPhase(bkeCluster *bkev1beta1.BKECluster) {
v, ok := annotation.HasAnnotation(bkeCluster, annotation.BKEClusterPauseAnnotationKey)
flag := ok && v == "true"
if bkeCluster.Spec.Pause && !flag {
for _, phase := range bkeCluster.Status.PhaseStatus {
if phase.Status == bkev1beta1.PhaseRunning && pc.Log != nil {
pc.Log.Info(constant.PhaseRunningReason, "BKECluster is paused, but phase %q is running, "+
"waiting for phase to complete", bkeCluster.Status.Phase)
}
}
}
}
func (pc *PhaseContext) handleDeletingBKECluster(bkeCluster *bkev1beta1.BKECluster) bool {
if bkeCluster.DeletionTimestamp == nil || bkeCluster.Status.ClusterStatus == bkev1beta1.ClusterDeleting {
return false
}
bkeCluster.Status.ClusterStatus = bkev1beta1.ClusterDeleting
if err := mergecluster.SyncStatusUntilComplete(pc.Client, bkeCluster); err != nil {
if pc.Log != nil {
pc.Log.Warn(constant.ReconcileErrorReason, "failed to update bkeCluster Status: %v", err)
}
}
if pc.Log != nil {
pc.Log.Info(constant.ClusterDeletingReason, "BKECluster is deleted, canceling phase context")
}
pc.Cancel()
return true
}
func (pc *PhaseContext) NodeFetcher() *nodeutil.NodeFetcher {
if pc.nodeFetcher == nil {
pc.nodeFetcher = nodeutil.NewNodeFetcher(pc.Client)
}
return pc.nodeFetcher
}
func (pc *PhaseContext) GetNodes() (bkenode.Nodes, error) {
return pc.NodeFetcher().GetNodesForBKECluster(pc.Context, pc.BKECluster)
}
func (pc *PhaseContext) GetBKENodes() (bkev1beta1.BKENodes, error) {
return pc.NodeFetcher().GetBKENodesWrapperForCluster(pc.Context, pc.BKECluster)
}
func (pc *PhaseContext) HasNodes() (bool, error) {
count, err := pc.NodeFetcher().GetNodeCountForCluster(pc.Context, pc.BKECluster)
if err != nil {
return false, err
}
return count > 0, nil
}
func (pc *PhaseContext) GetNodeStateFlag(ip string, flag int) (bool, error) {
return pc.NodeFetcher().GetNodeStateFlagForCluster(pc.Context, pc.BKECluster, ip, flag)
}
func (pc *PhaseContext) SetNodeStateWithMessage(ip string, state confv1beta1.NodeState, message string) error {
return pc.NodeFetcher().SetNodeStateWithMessageForCluster(pc.Context, pc.BKECluster, ip, state, message)
}
func (pc *PhaseContext) SetNodeStateMessage(ip string, message string) error {
return pc.NodeFetcher().SetBKENodeStateMessage(pc.Context, pc.BKECluster.Namespace, pc.BKECluster.Name, ip, message)
}