github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/policy_util_test.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  	"fmt"
    24  	"time"
    25  
    26  	"github.com/sethvargo/go-password/password"
    27  	appsv1 "k8s.io/api/apps/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/client-go/tools/record"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  	"sigs.k8s.io/controller-runtime/pkg/log"
    36  
    37  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    38  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    39  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    40  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    41  )
    42  
    43  var (
    44  	defaultNamespace = "default"
    45  	stsSchemaKind    = appsv1.SchemeGroupVersion.WithKind("StatefulSet")
    46  )
    47  
    48  func newMockDeployments(replicas int, name string, labels map[string]string) appsv1.Deployment {
    49  	uid, _ := password.Generate(12, 12, 0, true, false)
    50  	return appsv1.Deployment{
    51  		TypeMeta: metav1.TypeMeta{
    52  			Kind:       "StatefulSet",
    53  			APIVersion: "apps/v1",
    54  		},
    55  		ObjectMeta: metav1.ObjectMeta{
    56  			Name:      name,
    57  			Namespace: defaultNamespace,
    58  			UID:       types.UID(uid),
    59  		},
    60  		Spec: appsv1.DeploymentSpec{
    61  			Replicas: func() *int32 { i := int32(replicas); return &i }(),
    62  			Template: corev1.PodTemplateSpec{
    63  				ObjectMeta: metav1.ObjectMeta{
    64  					Labels: labels,
    65  				},
    66  				Spec: corev1.PodSpec{
    67  					Containers: []corev1.Container{},
    68  					Volumes: []corev1.Volume{{
    69  						Name: "for_test",
    70  						VolumeSource: corev1.VolumeSource{
    71  							HostPath: &corev1.HostPathVolumeSource{
    72  								Path: "/tmp",
    73  							},
    74  						}}},
    75  				},
    76  			},
    77  		},
    78  	}
    79  }
    80  
    81  func newMockStatefulSet(replicas int, name string, labels map[string]string) appsv1.StatefulSet {
    82  	uid, _ := password.Generate(12, 12, 0, true, false)
    83  	serviceName, _ := password.Generate(12, 0, 0, true, false)
    84  	return appsv1.StatefulSet{
    85  		TypeMeta: metav1.TypeMeta{
    86  			Kind:       "StatefulSet",
    87  			APIVersion: "apps/v1",
    88  		},
    89  		ObjectMeta: metav1.ObjectMeta{
    90  			Name:      name,
    91  			Namespace: defaultNamespace,
    92  			UID:       types.UID(uid),
    93  		},
    94  		Spec: appsv1.StatefulSetSpec{
    95  			Selector: &metav1.LabelSelector{
    96  				MatchLabels: labels,
    97  			},
    98  			Replicas: func() *int32 { i := int32(replicas); return &i }(),
    99  			Template: corev1.PodTemplateSpec{
   100  				ObjectMeta: metav1.ObjectMeta{
   101  					Labels: labels,
   102  				},
   103  				Spec: corev1.PodSpec{
   104  					Containers: []corev1.Container{},
   105  					Volumes: []corev1.Volume{{
   106  						Name: "for_test",
   107  						VolumeSource: corev1.VolumeSource{
   108  							HostPath: &corev1.HostPathVolumeSource{
   109  								Path: "/tmp",
   110  							},
   111  						}}},
   112  				},
   113  			},
   114  			ServiceName: serviceName,
   115  		},
   116  	}
   117  }
   118  
   119  type ParamsOps func(params *reconfigureParams)
   120  
   121  func withMockStatefulSet(replicas int, labels map[string]string) ParamsOps {
   122  	return func(params *reconfigureParams) {
   123  		rand, _ := password.Generate(12, 8, 0, true, false)
   124  		stsName := "test_" + rand
   125  		params.ComponentUnits = []appsv1.StatefulSet{
   126  			newMockStatefulSet(replicas, stsName, labels),
   127  		}
   128  	}
   129  }
   130  
   131  func withMockDeployments(replicas int, labels map[string]string) ParamsOps {
   132  	return func(params *reconfigureParams) {
   133  		rand, _ := password.Generate(12, 8, 0, true, false)
   134  		deployName := "test_" + rand
   135  		params.DeploymentUnits = []appsv1.Deployment{
   136  			newMockDeployments(replicas, deployName, labels),
   137  		}
   138  	}
   139  }
   140  
   141  func withClusterComponent(replicas int) ParamsOps {
   142  	return func(params *reconfigureParams) {
   143  		params.ClusterComponent = &appsv1alpha1.ClusterComponentSpec{
   144  			Name:     "test",
   145  			Replicas: func() int32 { rep := int32(replicas); return rep }(),
   146  		}
   147  	}
   148  }
   149  
   150  func withGRPCClient(clientFactory createReconfigureClient) ParamsOps {
   151  	return func(params *reconfigureParams) {
   152  		params.ReconfigureClientFactory = clientFactory
   153  	}
   154  }
   155  
   156  func withConfigSpec(configSpecName string, data map[string]string) ParamsOps {
   157  	return func(params *reconfigureParams) {
   158  		params.ConfigMap = &corev1.ConfigMap{
   159  			Data: data,
   160  		}
   161  		params.ConfigSpecName = configSpecName
   162  	}
   163  }
   164  
   165  func withConfigConstraintSpec(formatter *appsv1alpha1.FormatterConfig) ParamsOps {
   166  	return func(params *reconfigureParams) {
   167  		params.ConfigConstraint = &appsv1alpha1.ConfigConstraintSpec{
   168  			FormatterConfig: formatter,
   169  		}
   170  	}
   171  }
   172  
   173  func withConfigPatch(patch map[string]string) ParamsOps {
   174  	mockEmptyData := func(m map[string]string) map[string]string {
   175  		r := make(map[string]string, len(patch))
   176  		for key := range m {
   177  			r[key] = ""
   178  		}
   179  		return r
   180  	}
   181  	transKeyPair := func(pts map[string]string) map[string]interface{} {
   182  		m := make(map[string]interface{}, len(pts))
   183  		for key, value := range pts {
   184  			m[key] = value
   185  		}
   186  		return m
   187  	}
   188  	return func(params *reconfigureParams) {
   189  		cc := params.ConfigConstraint
   190  		newConfigData, _ := intctrlutil.MergeAndValidateConfigs(*cc, map[string]string{"for_test": ""}, nil, []core.ParamPairs{{
   191  			Key:           "for_test",
   192  			UpdatedParams: transKeyPair(patch),
   193  		}})
   194  		configPatch, _, _ := core.CreateConfigPatch(mockEmptyData(newConfigData), newConfigData, cc.FormatterConfig.Format, nil, false)
   195  		params.ConfigPatch = configPatch
   196  	}
   197  }
   198  
   199  func withCDComponent(compType appsv1alpha1.WorkloadType, tpls []appsv1alpha1.ComponentConfigSpec) ParamsOps {
   200  	return func(params *reconfigureParams) {
   201  		params.Component = &appsv1alpha1.ClusterComponentDefinition{
   202  			ConfigSpecs:  tpls,
   203  			WorkloadType: compType,
   204  			Name:         string(compType),
   205  		}
   206  		if compType == appsv1alpha1.Consensus || compType == appsv1alpha1.Replication {
   207  			params.Component.RSMSpec = &appsv1alpha1.RSMSpec{
   208  				Roles: []workloads.ReplicaRole{
   209  					{
   210  						Name:       "leader",
   211  						IsLeader:   true,
   212  						AccessMode: workloads.ReadWriteMode,
   213  						CanVote:    true,
   214  					},
   215  					{
   216  						Name:       "follower",
   217  						IsLeader:   false,
   218  						AccessMode: workloads.ReadonlyMode,
   219  						CanVote:    true,
   220  					},
   221  				},
   222  			}
   223  		}
   224  	}
   225  }
   226  
   227  func newMockReconfigureParams(testName string, cli client.Client, paramOps ...ParamsOps) reconfigureParams {
   228  	params := reconfigureParams{
   229  		Restart: true,
   230  		Client:  cli,
   231  		Ctx: intctrlutil.RequestCtx{
   232  			Ctx:      ctx,
   233  			Log:      log.FromContext(ctx).WithValues("policy_test", testName),
   234  			Recorder: record.NewFakeRecorder(100),
   235  		},
   236  		Cluster: &appsv1alpha1.Cluster{
   237  			ObjectMeta: metav1.ObjectMeta{
   238  				Name: "test",
   239  			}},
   240  	}
   241  	for _, customFn := range paramOps {
   242  		customFn(&params)
   243  	}
   244  	return params
   245  }
   246  
   247  func newMockPodsWithStatefulSet(sts *appsv1.StatefulSet, replicas int, options ...PodOptions) []corev1.Pod {
   248  	pods := make([]corev1.Pod, replicas)
   249  	for i := 0; i < replicas; i++ {
   250  		pods[i] = newMockPod(sts.Name+"-"+fmt.Sprint(i), &sts.Spec.Template.Spec)
   251  		pods[i].OwnerReferences = []metav1.OwnerReference{newControllerRef(sts, stsSchemaKind)}
   252  		pods[i].Status.PodIP = "1.1.1.1"
   253  	}
   254  	for _, customFn := range options {
   255  		for i := range pods {
   256  			pod := &pods[i]
   257  			customFn(pod, i)
   258  		}
   259  	}
   260  	return pods
   261  }
   262  
   263  func newMockPodsWithDeployment(deploy *appsv1.Deployment, replicas int, options ...PodOptions) []corev1.Pod {
   264  	pods := make([]corev1.Pod, replicas)
   265  	for i := 0; i < replicas; i++ {
   266  		pods[i] = newMockPod(deploy.Name+"-"+fmt.Sprint(i), &deploy.Spec.Template.Spec)
   267  		pods[i].OwnerReferences = []metav1.OwnerReference{newControllerRef(deploy, stsSchemaKind)}
   268  		pods[i].Status.PodIP = "1.1.1.1"
   269  	}
   270  	for _, customFn := range options {
   271  		for i := range pods {
   272  			pod := &pods[i]
   273  			customFn(pod, i)
   274  		}
   275  	}
   276  	return pods
   277  }
   278  
   279  func withReadyPod(rMin, rMax int) PodOptions {
   280  	return func(pod *corev1.Pod, index int) {
   281  		if index < rMin || index >= rMax {
   282  			return
   283  		}
   284  
   285  		if pod.Status.Conditions == nil {
   286  			pod.Status.Conditions = make([]corev1.PodCondition, 0)
   287  		}
   288  
   289  		pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{
   290  			Type:   corev1.PodReady,
   291  			Status: corev1.ConditionTrue,
   292  		})
   293  
   294  		pod.Status.Phase = corev1.PodRunning
   295  	}
   296  }
   297  
   298  func withAvailablePod(rMin, rMax int) PodOptions {
   299  	return func(pod *corev1.Pod, index int) {
   300  		if index < rMin || index >= rMax {
   301  			return
   302  		}
   303  
   304  		if pod.Status.Conditions == nil {
   305  			pod.Status.Conditions = make([]corev1.PodCondition, 0)
   306  		}
   307  
   308  		h, _ := time.ParseDuration("-1h")
   309  		pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{
   310  			Type:               corev1.PodReady,
   311  			Status:             corev1.ConditionTrue,
   312  			LastTransitionTime: metav1.NewTime(time.Now().Add(h)),
   313  		})
   314  		pod.Status.Phase = corev1.PodRunning
   315  	}
   316  }
   317  
   318  func fromPodObjectList(pods []corev1.Pod) []runtime.Object {
   319  	objs := make([]runtime.Object, len(pods))
   320  	for i := 0; i < len(pods); i++ {
   321  		objs[i] = &pods[i]
   322  	}
   323  	return objs
   324  }
   325  
   326  func newControllerRef(owner client.Object, gvk schema.GroupVersionKind) metav1.OwnerReference {
   327  	bRefFn := func(b bool) *bool { return &b }
   328  	return metav1.OwnerReference{
   329  		APIVersion:         gvk.GroupVersion().String(),
   330  		Kind:               gvk.Kind,
   331  		Name:               owner.GetName(),
   332  		UID:                owner.GetUID(),
   333  		Controller:         bRefFn(true),
   334  		BlockOwnerDeletion: bRefFn(false),
   335  	}
   336  }
   337  
   338  type PodOptions func(pod *corev1.Pod, index int)
   339  
   340  func newMockPod(podName string, podSpec *corev1.PodSpec) corev1.Pod {
   341  	pod := corev1.Pod{
   342  		ObjectMeta: metav1.ObjectMeta{
   343  			Name:      podName,
   344  			Namespace: defaultNamespace,
   345  		},
   346  	}
   347  	pod.Spec = *podSpec.DeepCopy()
   348  	return pod
   349  }