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(¶ms) 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 }