github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/reconfigure_policy.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package configuration
    21  
    22  import (
    23  	"math"
    24  
    25  	"google.golang.org/grpc"
    26  	"google.golang.org/grpc/credentials/insecure"
    27  	appsv1 "k8s.io/api/apps/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  
    31  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    32  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    33  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    34  	cfgproto "github.com/1aal/kubeblocks/pkg/configuration/proto"
    35  	"github.com/1aal/kubeblocks/pkg/configuration/util"
    36  	"github.com/1aal/kubeblocks/pkg/constant"
    37  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    38  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    39  )
    40  
    41  // ExecStatus defines running result for Reconfiguring policy (fsm).
    42  // ESNone describes policy has finished and quit.
    43  // ESRetry describes fsm is running.
    44  // ESFailed describes fsm is failed and exited.
    45  // ESNotSupport describes fsm does not support the feature.
    46  // ESFailedAndRetry describes fsm is failed in current state, but can be retried.
    47  // +enum
    48  type ExecStatus string
    49  
    50  const (
    51  	ESNone           ExecStatus = "None"
    52  	ESRetry          ExecStatus = "Retry"
    53  	ESFailed         ExecStatus = "Failed"
    54  	ESNotSupport     ExecStatus = "NotSupport"
    55  	ESFailedAndRetry ExecStatus = "FailedAndRetry"
    56  )
    57  
    58  type ReturnedStatus struct {
    59  	Status        ExecStatus
    60  	SucceedCount  int32
    61  	ExpectedCount int32
    62  }
    63  
    64  type reconfigurePolicy interface {
    65  	// Upgrade is to enable the configuration to take effect.
    66  	Upgrade(params reconfigureParams) (ReturnedStatus, error)
    67  
    68  	// GetPolicyName returns name of policy.
    69  	GetPolicyName() string
    70  }
    71  
    72  type AutoReloadPolicy struct{}
    73  
    74  type reconfigureParams struct {
    75  	// Only supports restart pod or container.
    76  	Restart bool
    77  
    78  	// Name is a config template name.
    79  	ConfigSpecName string
    80  
    81  	// Configuration files patch.
    82  	ConfigPatch *core.ConfigPatchInfo
    83  
    84  	// Configmap object of the configuration template instance in the component.
    85  	ConfigMap *corev1.ConfigMap
    86  
    87  	// ConfigConstraint pointer
    88  	ConfigConstraint *appsv1alpha1.ConfigConstraintSpec
    89  
    90  	// For grpc factory
    91  	ReconfigureClientFactory createReconfigureClient
    92  
    93  	// List of containers using this config volume.
    94  	ContainerNames []string
    95  
    96  	Client client.Client
    97  	Ctx    intctrlutil.RequestCtx
    98  
    99  	Cluster *appsv1alpha1.Cluster
   100  
   101  	// Associated component for cluster.
   102  	ClusterComponent *appsv1alpha1.ClusterComponentSpec
   103  	// Associated component for clusterdefinition.
   104  	Component *appsv1alpha1.ClusterComponentDefinition
   105  
   106  	// List of StatefulSets using this config template.
   107  	ComponentUnits []appsv1.StatefulSet
   108  	// List of Deployment using this config template.
   109  	DeploymentUnits []appsv1.Deployment
   110  	// List of ReplicatedStateMachine using this config template.
   111  	RSMList []workloads.ReplicatedStateMachine
   112  }
   113  
   114  var (
   115  	// lazy creation of grpc connection
   116  	// TODO support connection pool
   117  	newGRPCClient = func(addr string) (cfgproto.ReconfigureClient, error) {
   118  		conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		return cfgproto.NewReconfigureClient(conn), nil
   123  	}
   124  )
   125  
   126  var upgradePolicyMap = map[appsv1alpha1.UpgradePolicy]reconfigurePolicy{}
   127  
   128  func init() {
   129  	RegisterPolicy(appsv1alpha1.AutoReload, &AutoReloadPolicy{})
   130  }
   131  
   132  func (param *reconfigureParams) WorkloadType() appsv1alpha1.WorkloadType {
   133  	return param.Component.WorkloadType
   134  }
   135  
   136  // GetClientFactory support ut mock
   137  func GetClientFactory() createReconfigureClient {
   138  	return newGRPCClient
   139  }
   140  
   141  func (param *reconfigureParams) getConfigKey() string {
   142  	return param.ConfigSpecName
   143  }
   144  
   145  func (param *reconfigureParams) getTargetVersionHash() string {
   146  	hash, err := util.ComputeHash(param.ConfigMap.Data)
   147  	if err != nil {
   148  		param.Ctx.Log.Error(err, "failed to get configuration version!")
   149  		return ""
   150  	}
   151  
   152  	return hash
   153  }
   154  
   155  func (param *reconfigureParams) maxRollingReplicas() int32 {
   156  	var (
   157  		defaultRolling int32 = 1
   158  		r              int32
   159  		replicas       = param.getTargetReplicas()
   160  	)
   161  
   162  	if param.Component.GetMaxUnavailable() == nil {
   163  		return defaultRolling
   164  	}
   165  
   166  	v, isPercentage, err := intctrlutil.GetIntOrPercentValue(param.Component.GetMaxUnavailable())
   167  	if err != nil {
   168  		param.Ctx.Log.Error(err, "failed to get maxUnavailable!")
   169  		return defaultRolling
   170  	}
   171  
   172  	if isPercentage {
   173  		r = int32(math.Floor(float64(v) * float64(replicas) / 100))
   174  	} else {
   175  		r = util.Safe2Int32(util.Min(v, param.getTargetReplicas()))
   176  	}
   177  	return util.Max(r, defaultRolling)
   178  }
   179  
   180  func (param *reconfigureParams) getTargetReplicas() int {
   181  	return int(param.ClusterComponent.Replicas)
   182  }
   183  
   184  func (param *reconfigureParams) podMinReadySeconds() int32 {
   185  	minReadySeconds := param.ComponentUnits[0].Spec.MinReadySeconds
   186  	return util.Max(minReadySeconds, viper.GetInt32(constant.PodMinReadySecondsEnv))
   187  }
   188  
   189  func RegisterPolicy(policy appsv1alpha1.UpgradePolicy, action reconfigurePolicy) {
   190  	upgradePolicyMap[policy] = action
   191  }
   192  
   193  func (receiver AutoReloadPolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) {
   194  	_ = params
   195  	return makeReturnedStatus(ESNone), nil
   196  }
   197  
   198  func (receiver AutoReloadPolicy) GetPolicyName() string {
   199  	return string(appsv1alpha1.AutoReload)
   200  }
   201  
   202  func NewReconfigurePolicy(cc *appsv1alpha1.ConfigConstraintSpec, cfgPatch *core.ConfigPatchInfo, policy appsv1alpha1.UpgradePolicy, restart bool) (reconfigurePolicy, error) {
   203  	if cfgPatch != nil && !cfgPatch.IsModify {
   204  		// not walk here
   205  		return nil, core.MakeError("cfg not modify. [%v]", cfgPatch)
   206  	}
   207  
   208  	if enableAutoDecision(restart, policy) {
   209  		if dynamicUpdate, err := core.IsUpdateDynamicParameters(cc, cfgPatch); err != nil {
   210  			return nil, err
   211  		} else if dynamicUpdate {
   212  			policy = appsv1alpha1.AutoReload
   213  		}
   214  		if enableSyncReload(policy, cc.ReloadOptions) {
   215  			policy = appsv1alpha1.OperatorSyncUpdate
   216  		}
   217  	}
   218  	if policy == appsv1alpha1.NonePolicy {
   219  		policy = appsv1alpha1.NormalPolicy
   220  	}
   221  	if action, ok := upgradePolicyMap[policy]; ok {
   222  		return action, nil
   223  	}
   224  	return nil, core.MakeError("not supported upgrade policy:[%s]", policy)
   225  }
   226  
   227  func enableAutoDecision(restart bool, policy appsv1alpha1.UpgradePolicy) bool {
   228  	return !restart && policy == appsv1alpha1.NonePolicy
   229  }
   230  
   231  func enableSyncReload(policyType appsv1alpha1.UpgradePolicy, options *appsv1alpha1.ReloadOptions) bool {
   232  	return policyType == appsv1alpha1.AutoReload && enableSyncTrigger(options)
   233  }
   234  
   235  func enableSyncTrigger(options *appsv1alpha1.ReloadOptions) bool {
   236  	if options == nil {
   237  		return false
   238  	}
   239  
   240  	if options.TPLScriptTrigger != nil {
   241  		return !core.IsWatchModuleForTplTrigger(options.TPLScriptTrigger)
   242  	}
   243  
   244  	if options.ShellTrigger != nil {
   245  		return !core.IsWatchModuleForShellTrigger(options.ShellTrigger)
   246  	}
   247  	return false
   248  }
   249  
   250  func withSucceed(succeedCount int32) func(status *ReturnedStatus) {
   251  	return func(status *ReturnedStatus) {
   252  		status.SucceedCount = succeedCount
   253  	}
   254  }
   255  
   256  func withExpected(expectedCount int32) func(status *ReturnedStatus) {
   257  	return func(status *ReturnedStatus) {
   258  		status.ExpectedCount = expectedCount
   259  	}
   260  }
   261  
   262  func makeReturnedStatus(status ExecStatus, ops ...func(status *ReturnedStatus)) ReturnedStatus {
   263  	ret := ReturnedStatus{
   264  		Status:        status,
   265  		SucceedCount:  core.Unconfirmed,
   266  		ExpectedCount: core.Unconfirmed,
   267  	}
   268  	for _, o := range ops {
   269  		o(&ret)
   270  	}
   271  	return ret
   272  }
   273  
   274  func fromWorkloadObjects(params reconfigureParams) []client.Object {
   275  	r := make([]client.Object, 0)
   276  	for _, unit := range params.RSMList {
   277  		r = append(r, &unit)
   278  	}
   279  	// migrated workload
   280  	if len(r) != 0 {
   281  		return r
   282  	}
   283  	for _, unit := range params.ComponentUnits {
   284  		r = append(r, &unit)
   285  	}
   286  	for _, unit := range params.DeploymentUnits {
   287  		r = append(r, &unit)
   288  	}
   289  	return r
   290  }