k8s.io/kubernetes@v1.29.3/test/integration/statefulset/util.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package statefulset 18 19 import ( 20 "context" 21 "fmt" 22 "sync" 23 "testing" 24 "time" 25 26 appsv1 "k8s.io/api/apps/v1" 27 v1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/resource" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/labels" 31 "k8s.io/apimachinery/pkg/util/wait" 32 "k8s.io/apiserver/pkg/admission" 33 "k8s.io/client-go/informers" 34 clientset "k8s.io/client-go/kubernetes" 35 typedappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" 36 typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" 37 restclient "k8s.io/client-go/rest" 38 "k8s.io/client-go/util/retry" 39 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 40 api "k8s.io/kubernetes/pkg/apis/core" 41 42 //svc "k8s.io/kubernetes/pkg/api/v1/service" 43 "k8s.io/kubernetes/pkg/controller/statefulset" 44 "k8s.io/kubernetes/test/integration/framework" 45 ) 46 47 const ( 48 pollInterval = 100 * time.Millisecond 49 pollTimeout = 60 * time.Second 50 ) 51 52 func labelMap() map[string]string { 53 return map[string]string{"foo": "bar"} 54 } 55 56 // newService returns a service with a fake name for StatefulSet to be created soon 57 func newHeadlessService(namespace string) *v1.Service { 58 return &v1.Service{ 59 TypeMeta: metav1.TypeMeta{ 60 Kind: "Service", 61 APIVersion: "v1", 62 }, 63 ObjectMeta: metav1.ObjectMeta{ 64 Namespace: namespace, 65 Name: "fake-service-name", 66 }, 67 Spec: v1.ServiceSpec{ 68 ClusterIP: "None", 69 Ports: []v1.ServicePort{ 70 {Port: 80, Name: "http", Protocol: "TCP"}, 71 }, 72 Selector: labelMap(), 73 }, 74 } 75 } 76 77 // newSTS returns a StatefulSet with a fake container image 78 func newSTS(name, namespace string, replicas int) *appsv1.StatefulSet { 79 replicasCopy := int32(replicas) 80 return &appsv1.StatefulSet{ 81 TypeMeta: metav1.TypeMeta{ 82 Kind: "StatefulSet", 83 APIVersion: "apps/v1", 84 }, 85 ObjectMeta: metav1.ObjectMeta{ 86 Namespace: namespace, 87 Name: name, 88 }, 89 Spec: appsv1.StatefulSetSpec{ 90 PodManagementPolicy: appsv1.ParallelPodManagement, 91 Replicas: &replicasCopy, 92 Selector: &metav1.LabelSelector{ 93 MatchLabels: labelMap(), 94 }, 95 Template: v1.PodTemplateSpec{ 96 ObjectMeta: metav1.ObjectMeta{ 97 Labels: labelMap(), 98 }, 99 Spec: v1.PodSpec{ 100 Containers: []v1.Container{ 101 { 102 Name: "fake-name", 103 Image: "fakeimage", 104 VolumeMounts: []v1.VolumeMount{ 105 {Name: "datadir", MountPath: "/data/"}, 106 {Name: "home", MountPath: "/home"}, 107 }, 108 }, 109 }, 110 Volumes: []v1.Volume{ 111 { 112 Name: "datadir", 113 VolumeSource: v1.VolumeSource{ 114 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 115 ClaimName: "fake-pvc-name", 116 }, 117 }, 118 }, 119 { 120 Name: "home", 121 VolumeSource: v1.VolumeSource{ 122 HostPath: &v1.HostPathVolumeSource{ 123 Path: fmt.Sprintf("/tmp/%v", "home"), 124 }, 125 }, 126 }, 127 }, 128 }, 129 }, 130 ServiceName: "fake-service-name", 131 UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ 132 Type: appsv1.RollingUpdateStatefulSetStrategyType, 133 }, 134 VolumeClaimTemplates: []v1.PersistentVolumeClaim{ 135 // for volume mount "datadir" 136 newStatefulSetPVC("fake-pvc-name"), 137 }, 138 }, 139 } 140 } 141 142 func newStatefulSetPVC(name string) v1.PersistentVolumeClaim { 143 return v1.PersistentVolumeClaim{ 144 ObjectMeta: metav1.ObjectMeta{ 145 Name: name, 146 Annotations: map[string]string{ 147 "volume.alpha.kubernetes.io/storage-class": "anything", 148 }, 149 }, 150 Spec: v1.PersistentVolumeClaimSpec{ 151 AccessModes: []v1.PersistentVolumeAccessMode{ 152 v1.ReadWriteOnce, 153 }, 154 Resources: v1.VolumeResourceRequirements{ 155 Requests: v1.ResourceList{ 156 v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI), 157 }, 158 }, 159 }, 160 } 161 } 162 163 // scSetup sets up necessities for Statefulset integration test, including control plane, apiserver, informers, and clientset 164 func scSetup(ctx context.Context, t *testing.T) (kubeapiservertesting.TearDownFunc, *statefulset.StatefulSetController, informers.SharedInformerFactory, clientset.Interface) { 165 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 166 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 167 168 config := restclient.CopyConfig(server.ClientConfig) 169 clientSet, err := clientset.NewForConfig(config) 170 if err != nil { 171 t.Fatalf("error in create clientset: %v", err) 172 } 173 resyncPeriod := 12 * time.Hour 174 informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "statefulset-informers")), resyncPeriod) 175 176 sc := statefulset.NewStatefulSetController( 177 ctx, 178 informers.Core().V1().Pods(), 179 informers.Apps().V1().StatefulSets(), 180 informers.Core().V1().PersistentVolumeClaims(), 181 informers.Apps().V1().ControllerRevisions(), 182 clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "statefulset-controller")), 183 ) 184 185 return server.TearDownFn, sc, informers, clientSet 186 } 187 188 // Run STS controller and informers 189 func runControllerAndInformers(sc *statefulset.StatefulSetController, informers informers.SharedInformerFactory) context.CancelFunc { 190 ctx, cancel := context.WithCancel(context.Background()) 191 informers.Start(ctx.Done()) 192 go sc.Run(ctx, 5) 193 return cancel 194 } 195 196 func createHeadlessService(t *testing.T, clientSet clientset.Interface, headlessService *v1.Service) { 197 _, err := clientSet.CoreV1().Services(headlessService.Namespace).Create(context.TODO(), headlessService, metav1.CreateOptions{}) 198 if err != nil { 199 t.Fatalf("failed creating headless service: %v", err) 200 } 201 } 202 203 func createSTSs(t *testing.T, clientSet clientset.Interface, stss []*appsv1.StatefulSet) []*appsv1.StatefulSet { 204 var createdSTSs []*appsv1.StatefulSet 205 for _, sts := range stss { 206 createdSTS, err := clientSet.AppsV1().StatefulSets(sts.Namespace).Create(context.TODO(), sts, metav1.CreateOptions{}) 207 if err != nil { 208 t.Fatalf("failed to create sts %s: %v", sts.Name, err) 209 } 210 createdSTSs = append(createdSTSs, createdSTS) 211 } 212 return createdSTSs 213 } 214 215 func createPods(t *testing.T, clientSet clientset.Interface, pods []*v1.Pod) []*v1.Pod { 216 var createdPods []*v1.Pod 217 for _, pod := range pods { 218 createdPod, err := clientSet.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 219 if err != nil { 220 t.Fatalf("failed to create pod %s: %v", pod.Name, err) 221 } 222 createdPods = append(createdPods, createdPod) 223 } 224 225 return createdPods 226 } 227 228 func createSTSsPods(t *testing.T, clientSet clientset.Interface, stss []*appsv1.StatefulSet, pods []*v1.Pod) ([]*appsv1.StatefulSet, []*v1.Pod) { 229 return createSTSs(t, clientSet, stss), createPods(t, clientSet, pods) 230 } 231 232 // Verify .Status.Replicas is equal to .Spec.Replicas 233 func waitSTSStable(t *testing.T, clientSet clientset.Interface, sts *appsv1.StatefulSet) { 234 stsClient := clientSet.AppsV1().StatefulSets(sts.Namespace) 235 desiredGeneration := sts.Generation 236 if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) { 237 newSTS, err := stsClient.Get(context.TODO(), sts.Name, metav1.GetOptions{}) 238 if err != nil { 239 return false, err 240 } 241 return newSTS.Status.Replicas == *newSTS.Spec.Replicas && newSTS.Status.ObservedGeneration >= desiredGeneration, nil 242 }); err != nil { 243 t.Fatalf("failed to verify .Status.Replicas is equal to .Spec.Replicas for sts %s: %v", sts.Name, err) 244 } 245 } 246 247 func updatePod(t *testing.T, podClient typedv1.PodInterface, podName string, updateFunc func(*v1.Pod)) *v1.Pod { 248 var pod *v1.Pod 249 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 250 newPod, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{}) 251 if err != nil { 252 return err 253 } 254 updateFunc(newPod) 255 pod, err = podClient.Update(context.TODO(), newPod, metav1.UpdateOptions{}) 256 return err 257 }); err != nil { 258 t.Fatalf("failed to update pod %s: %v", podName, err) 259 } 260 return pod 261 } 262 263 func updatePodStatus(t *testing.T, podClient typedv1.PodInterface, podName string, updateStatusFunc func(*v1.Pod)) *v1.Pod { 264 var pod *v1.Pod 265 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 266 newPod, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{}) 267 if err != nil { 268 return err 269 } 270 updateStatusFunc(newPod) 271 pod, err = podClient.UpdateStatus(context.TODO(), newPod, metav1.UpdateOptions{}) 272 return err 273 }); err != nil { 274 t.Fatalf("failed to update status of pod %s: %v", podName, err) 275 } 276 return pod 277 } 278 279 func getPods(t *testing.T, podClient typedv1.PodInterface, labelMap map[string]string) *v1.PodList { 280 podSelector := labels.Set(labelMap).AsSelector() 281 options := metav1.ListOptions{LabelSelector: podSelector.String()} 282 pods, err := podClient.List(context.TODO(), options) 283 if err != nil { 284 t.Fatalf("failed obtaining a list of pods that match the pod labels %v: %v", labelMap, err) 285 } 286 if pods == nil { 287 t.Fatalf("obtained a nil list of pods") 288 } 289 return pods 290 } 291 292 func getStatefulSetPVCs(t *testing.T, pvcClient typedv1.PersistentVolumeClaimInterface, sts *appsv1.StatefulSet) []*v1.PersistentVolumeClaim { 293 pvcs := []*v1.PersistentVolumeClaim{} 294 for i := int32(0); i < *sts.Spec.Replicas; i++ { 295 pvcName := fmt.Sprintf("%s-%s-%d", sts.Spec.VolumeClaimTemplates[0].Name, sts.Name, i) 296 pvc, err := pvcClient.Get(context.TODO(), pvcName, metav1.GetOptions{}) 297 if err != nil { 298 t.Fatalf("failed to get PVC %s: %v", pvcName, err) 299 } 300 pvcs = append(pvcs, pvc) 301 } 302 return pvcs 303 } 304 305 func verifyOwnerRef(t *testing.T, pvc *v1.PersistentVolumeClaim, kind string, expected bool) { 306 found := false 307 for _, ref := range pvc.GetOwnerReferences() { 308 if ref.Kind == kind { 309 if expected { 310 found = true 311 } else { 312 t.Fatalf("Found %s ref but expected none for PVC %s", kind, pvc.Name) 313 } 314 } 315 } 316 if expected && !found { 317 t.Fatalf("Expected %s ref but found none for PVC %s", kind, pvc.Name) 318 } 319 } 320 321 func updateSTS(t *testing.T, stsClient typedappsv1.StatefulSetInterface, stsName string, updateFunc func(*appsv1.StatefulSet)) *appsv1.StatefulSet { 322 var sts *appsv1.StatefulSet 323 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 324 newSTS, err := stsClient.Get(context.TODO(), stsName, metav1.GetOptions{}) 325 if err != nil { 326 return err 327 } 328 updateFunc(newSTS) 329 sts, err = stsClient.Update(context.TODO(), newSTS, metav1.UpdateOptions{}) 330 return err 331 }); err != nil { 332 t.Fatalf("failed to update sts %s: %v", stsName, err) 333 } 334 return sts 335 } 336 337 // Update .Spec.Replicas to replicas and verify .Status.Replicas is changed accordingly 338 func scaleSTS(t *testing.T, c clientset.Interface, sts *appsv1.StatefulSet, replicas int32) { 339 stsClient := c.AppsV1().StatefulSets(sts.Namespace) 340 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 341 newSTS, err := stsClient.Get(context.TODO(), sts.Name, metav1.GetOptions{}) 342 if err != nil { 343 return err 344 } 345 *newSTS.Spec.Replicas = replicas 346 sts, err = stsClient.Update(context.TODO(), newSTS, metav1.UpdateOptions{}) 347 return err 348 }); err != nil { 349 t.Fatalf("failed to update .Spec.Replicas to %d for sts %s: %v", replicas, sts.Name, err) 350 } 351 waitSTSStable(t, c, sts) 352 } 353 354 var _ admission.ValidationInterface = &fakePodFailAdmission{} 355 356 type fakePodFailAdmission struct { 357 lock sync.Mutex 358 limitedPodNumber int 359 succeedPodsCount int 360 } 361 362 func (f *fakePodFailAdmission) Handles(operation admission.Operation) bool { 363 return operation == admission.Create 364 } 365 366 func (f *fakePodFailAdmission) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) (err error) { 367 if attr.GetKind().GroupKind() != api.Kind("Pod") { 368 return nil 369 } 370 371 f.lock.Lock() 372 defer f.lock.Unlock() 373 374 if f.succeedPodsCount >= f.limitedPodNumber { 375 return fmt.Errorf("fakePodFailAdmission error") 376 } 377 f.succeedPodsCount++ 378 return nil 379 }