github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/policy_util.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  	"context"
    24  	"fmt"
    25  	"net"
    26  	"sort"
    27  	"strconv"
    28  
    29  	appv1 "k8s.io/api/apps/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  
    33  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    34  	"github.com/1aal/kubeblocks/controllers/apps/components"
    35  	"github.com/1aal/kubeblocks/pkg/common"
    36  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    37  	cfgproto "github.com/1aal/kubeblocks/pkg/configuration/proto"
    38  	"github.com/1aal/kubeblocks/pkg/constant"
    39  	"github.com/1aal/kubeblocks/pkg/controller/rsm"
    40  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    41  	"github.com/1aal/kubeblocks/pkg/generics"
    42  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    43  )
    44  
    45  func getDeploymentRollingPods(params reconfigureParams) ([]corev1.Pod, error) {
    46  	// util.GetComponentPodList supports deployment
    47  	return getReplicationSetPods(params)
    48  }
    49  
    50  func getReplicationSetPods(params reconfigureParams) ([]corev1.Pod, error) {
    51  	var ctx = params.Ctx
    52  	var cluster = params.Cluster
    53  	podList, err := components.GetComponentPodList(ctx.Ctx, params.Client, *cluster, params.ClusterComponent.Name)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return podList.Items, nil
    58  }
    59  
    60  // GetComponentPods gets all pods of the component.
    61  func GetComponentPods(params reconfigureParams) ([]corev1.Pod, error) {
    62  	componentPods := make([]corev1.Pod, 0)
    63  	for i := range params.ComponentUnits {
    64  		pods, err := common.GetPodListByStatefulSet(params.Ctx.Ctx, params.Client, &params.ComponentUnits[i])
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  		componentPods = append(componentPods, pods...)
    69  	}
    70  	return componentPods, nil
    71  }
    72  
    73  // CheckReconfigureUpdateProgress checks pods of the component is ready.
    74  func CheckReconfigureUpdateProgress(pods []corev1.Pod, configKey, version string) int32 {
    75  	var (
    76  		readyPods        int32 = 0
    77  		cfgAnnotationKey       = core.GenerateUniqKeyWithConfig(constant.UpgradeRestartAnnotationKey, configKey)
    78  	)
    79  
    80  	for _, pod := range pods {
    81  		annotations := pod.Annotations
    82  		if len(annotations) != 0 && annotations[cfgAnnotationKey] == version && intctrlutil.PodIsReady(&pod) {
    83  			readyPods++
    84  		}
    85  	}
    86  	return readyPods
    87  }
    88  
    89  func getStatefulSetPods(params reconfigureParams) ([]corev1.Pod, error) {
    90  	if len(params.ComponentUnits) != 1 {
    91  		return nil, core.MakeError("statefulSet component require only one statefulset, actual %d components", len(params.ComponentUnits))
    92  	}
    93  
    94  	pods, err := GetComponentPods(params)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	sort.SliceStable(pods, func(i, j int) bool {
   100  		_, ordinal1 := intctrlutil.GetParentNameAndOrdinal(&pods[i])
   101  		_, ordinal2 := intctrlutil.GetParentNameAndOrdinal(&pods[j])
   102  		return ordinal1 < ordinal2
   103  	})
   104  	return pods, nil
   105  }
   106  
   107  func getConsensusPods(params reconfigureParams) ([]corev1.Pod, error) {
   108  	if len(params.ComponentUnits) > 1 {
   109  		return nil, core.MakeError("consensus component require only one statefulset, actual %d components", len(params.ComponentUnits))
   110  	}
   111  
   112  	if len(params.ComponentUnits) == 0 {
   113  		return nil, nil
   114  	}
   115  
   116  	pods, err := GetComponentPods(params)
   117  	// stsObj := &params.ComponentUnits[0]
   118  	// pods, err := components.GetPodListByStatefulSetWithSelector(params.Ctx.Ctx, params.Client, stsObj, client.MatchingLabels{
   119  	//	constant.KBAppComponentLabelKey: params.ClusterComponent.Name,
   120  	//	constant.AppInstanceLabelKey:    params.Cluster.Name,
   121  	// })
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	// TODO: should resolve the dependency on consensus module
   127  	if params.Component.RSMSpec != nil {
   128  		rsm.SortPods(pods, rsm.ComposeRolePriorityMap(params.Component.RSMSpec.Roles), true)
   129  	}
   130  	return pods, nil
   131  }
   132  
   133  // TODO commonOnlineUpdateWithPod migrate to sql command pipeline
   134  func commonOnlineUpdateWithPod(pod *corev1.Pod, ctx context.Context, createClient createReconfigureClient, configSpec string, updatedParams map[string]string) error {
   135  	address, err := cfgManagerGrpcURL(pod)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	client, err := createClient(address)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	response, err := client.OnlineUpgradeParams(ctx, &cfgproto.OnlineUpgradeParamsRequest{
   145  		ConfigSpec: configSpec,
   146  		Params:     updatedParams,
   147  	})
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	errMessage := response.GetErrMessage()
   153  	if errMessage != "" {
   154  		return core.MakeError(errMessage)
   155  	}
   156  	return nil
   157  }
   158  
   159  func commonStopContainerWithPod(pod *corev1.Pod, ctx context.Context, containerNames []string, createClient createReconfigureClient) error {
   160  	containerIDs := make([]string, 0, len(containerNames))
   161  	for _, name := range containerNames {
   162  		containerID := intctrlutil.GetContainerID(pod, name)
   163  		if containerID == "" {
   164  			return core.MakeError("failed to find container in pod[%s], name=%s", name, pod.Name)
   165  		}
   166  		containerIDs = append(containerIDs, containerID)
   167  	}
   168  
   169  	address, err := cfgManagerGrpcURL(pod)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	// stop container
   174  	client, err := createClient(address)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	response, err := client.StopContainer(ctx, &cfgproto.StopContainerRequest{
   180  		ContainerIDs: containerIDs,
   181  	})
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	errMessage := response.GetErrMessage()
   187  	if errMessage != "" {
   188  		return core.MakeError(errMessage)
   189  	}
   190  	return nil
   191  }
   192  
   193  func cfgManagerGrpcURL(pod *corev1.Pod) (string, error) {
   194  	podPort := viper.GetInt(constant.ConfigManagerGPRCPortEnv)
   195  	return getURLFromPod(pod, podPort)
   196  }
   197  
   198  func getURLFromPod(pod *corev1.Pod, portPort int) (string, error) {
   199  	ip := net.ParseIP(pod.Status.PodIP)
   200  	if ip == nil {
   201  		return "", core.MakeError("%s is not a valid IP", pod.Status.PodIP)
   202  	}
   203  
   204  	// Sanity check PodIP
   205  	if ip.To4() == nil && ip.To16() == nil {
   206  		return "", fmt.Errorf("%s is not a valid IPv4/IPv6 address", pod.Status.PodIP)
   207  	}
   208  	return net.JoinHostPort(ip.String(), strconv.Itoa(portPort)), nil
   209  }
   210  
   211  func restartWorkloadComponent[T generics.Object, PT generics.PObject[T], L generics.ObjList[T], PL generics.PObjList[T, L]](cli client.Client, ctx context.Context, annotationKey, annotationValue string, obj PT, _ func(T, PT, L, PL)) error {
   212  	template := transformPodTemplate(obj)
   213  	if updatedVersion(template, annotationKey, annotationValue) {
   214  		return nil
   215  	}
   216  
   217  	patch := client.MergeFrom(PT(obj.DeepCopy()))
   218  	if template.Annotations == nil {
   219  		template.Annotations = map[string]string{}
   220  	}
   221  	template.Annotations[annotationKey] = annotationValue
   222  	if err := cli.Patch(ctx, obj, patch); err != nil {
   223  		return err
   224  	}
   225  	return nil
   226  }
   227  
   228  func restartComponent(cli client.Client, ctx intctrlutil.RequestCtx, configKey string, newVersion string, objs []client.Object, recordEvent func(obj client.Object)) (client.Object, error) {
   229  	var err error
   230  	cfgAnnotationKey := core.GenerateUniqKeyWithConfig(constant.UpgradeRestartAnnotationKey, configKey)
   231  	for _, obj := range objs {
   232  		switch w := obj.(type) {
   233  		case *appv1.StatefulSet:
   234  			err = restartWorkloadComponent(cli, ctx.Ctx, cfgAnnotationKey, newVersion, w, generics.StatefulSetSignature)
   235  		case *appv1.Deployment:
   236  			err = restartWorkloadComponent(cli, ctx.Ctx, cfgAnnotationKey, newVersion, w, generics.DeploymentSignature)
   237  		case *workloads.ReplicatedStateMachine:
   238  			err = restartWorkloadComponent(cli, ctx.Ctx, cfgAnnotationKey, newVersion, w, generics.RSMSignature)
   239  		default:
   240  			// ignore other types workload
   241  		}
   242  		if err != nil {
   243  			return obj, err
   244  		}
   245  		if recordEvent != nil {
   246  			recordEvent(obj)
   247  		}
   248  	}
   249  	return nil, nil
   250  }
   251  
   252  func updatedVersion(podTemplate *corev1.PodTemplateSpec, keyPath, expectedVersion string) bool {
   253  	return podTemplate.Annotations != nil && podTemplate.Annotations[keyPath] == expectedVersion
   254  }