11package pgcluster
22
33import (
4- "bytes"
54 "context"
65 "crypto/md5"
76 "fmt"
@@ -11,21 +10,17 @@ import (
1110 "strings"
1211 "time"
1312
14- gover "github.com/hashicorp/go-version"
1513 "github.com/pkg/errors"
1614 "go.opentelemetry.io/otel/trace"
1715 batchv1 "k8s.io/api/batch/v1"
1816 corev1 "k8s.io/api/core/v1"
1917 k8serrors "k8s.io/apimachinery/pkg/api/errors"
20- "k8s.io/apimachinery/pkg/api/resource"
2118 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2219 "k8s.io/apimachinery/pkg/labels"
2320 "k8s.io/apimachinery/pkg/types"
24- "k8s.io/apimachinery/pkg/util/wait"
2521 "k8s.io/client-go/tools/record"
2622 "k8s.io/client-go/util/retry"
2723 "k8s.io/client-go/util/workqueue"
28- "k8s.io/utils/ptr"
2924 ctrl "sigs.k8s.io/controller-runtime"
3025 "sigs.k8s.io/controller-runtime/pkg/builder"
3126 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -39,11 +34,9 @@ import (
3934 "sigs.k8s.io/controller-runtime/pkg/source"
4035
4136 "github.com/percona/percona-postgresql-operator/v2/internal/controller/runtime"
42- "github.com/percona/percona-postgresql-operator/v2/internal/initialize"
4337 "github.com/percona/percona-postgresql-operator/v2/internal/logging"
4438 "github.com/percona/percona-postgresql-operator/v2/internal/naming"
4539 "github.com/percona/percona-postgresql-operator/v2/internal/postgres"
46- "github.com/percona/percona-postgresql-operator/v2/percona/clientcmd"
4740 perconaController "github.com/percona/percona-postgresql-operator/v2/percona/controller"
4841 "github.com/percona/percona-postgresql-operator/v2/percona/extensions"
4942 "github.com/percona/percona-postgresql-operator/v2/percona/k8s"
@@ -272,7 +265,7 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R
272265 return reconcile.Result {}, errors .Wrap (err , "ensure finalizers" )
273266 }
274267
275- if err := r .reconcilePatroniVersionCheck (ctx , cr ); err != nil {
268+ if err := r .reconcilePatroniVersion (ctx , cr ); err != nil {
276269 if errors .Is (err , errPatroniVersionCheckWait ) {
277270 return reconcile.Result {
278271 RequeueAfter : 5 * time .Second ,
@@ -366,231 +359,6 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R
366359 return ctrl.Result {}, nil
367360}
368361
369- var errPatroniVersionCheckWait = errors .New ("waiting for pod to initialize" )
370-
371- func (r * PGClusterReconciler ) reconcilePatroniVersionCheck (ctx context.Context , cr * v2.PerconaPGCluster ) error {
372- if cr .Annotations == nil {
373- cr .Annotations = make (map [string ]string )
374- }
375-
376- if patroniVersion , ok := cr .Annotations [pNaming .AnnotationCustomPatroniVersion ]; ok {
377- patroniVersionUpdateFunc := func () error {
378- cluster := & v2.PerconaPGCluster {}
379- if err := r .Client .Get (ctx , types.NamespacedName {
380- Name : cr .Name ,
381- Namespace : cr .Namespace ,
382- }, cluster ); err != nil {
383- return errors .Wrap (err , "get PerconaPGCluster" )
384- }
385-
386- orig := cluster .DeepCopy ()
387-
388- cluster .Status .Patroni .Version = patroniVersion
389- cluster .Status .PatroniVersion = patroniVersion
390-
391- if err := r .Client .Status ().Patch (ctx , cluster .DeepCopy (), client .MergeFrom (orig )); err != nil {
392- return errors .Wrap (err , "failed to patch patroni version" )
393- }
394-
395- err := r .patchPatroniVersionAnnotation (ctx , cr , patroniVersion )
396- if err != nil {
397- return errors .Wrap (err , "failed to patch patroni version annotation" )
398- }
399-
400- return nil
401- }
402-
403- // To ensure that the update was done given that conflicts can be caused by
404- // other code making unrelated updates to the same resource at the same time.
405- if err := retry .RetryOnConflict (retry .DefaultRetry , patroniVersionUpdateFunc ); err != nil {
406- return errors .Wrap (err , "failed to patch patroni version" )
407- }
408- return nil
409- }
410-
411- imageIDs , err := r .instanceImageIDs (ctx , cr )
412- if err != nil {
413- return errors .Wrap (err , "get image IDs" )
414- }
415-
416- // If the imageIDs slice contains the imageID from the status, we skip checking the Patroni version.
417- // This ensures that the Patroni version is only checked after all pods have been updated.
418- if cr .CompareVersion ("2.8.0" ) >= 0 {
419- if (len (imageIDs ) == 0 || slices .Contains (imageIDs , cr .Status .Postgres .ImageID )) && cr .Status .Patroni .Version != "" {
420- err = r .patchPatroniVersionAnnotation (ctx , cr , cr .Status .Patroni .Version )
421- if err != nil {
422- return errors .Wrap (err , "failed to patch patroni version annotation" )
423- }
424- return nil
425- }
426- } else {
427- if (len (imageIDs ) == 0 || slices .Contains (imageIDs , cr .Status .Postgres .ImageID )) && cr .Status .PatroniVersion != "" {
428- err = r .patchPatroniVersionAnnotation (ctx , cr , cr .Status .PatroniVersion )
429- if err != nil {
430- return errors .Wrap (err , "failed to patch patroni version annotation" )
431- }
432- return nil
433- }
434- }
435-
436- meta := metav1.ObjectMeta {
437- Name : cr .Name + "-patroni-version-check" ,
438- Namespace : cr .Namespace ,
439- }
440-
441- p := & corev1.Pod {
442- ObjectMeta : meta ,
443- }
444-
445- err = r .Client .Get (ctx , client .ObjectKeyFromObject (p ), p )
446- if client .IgnoreNotFound (err ) != nil {
447- return errors .Wrap (err , "failed to get patroni version check pod" )
448- }
449- if k8serrors .IsNotFound (err ) {
450- if len (cr .Spec .InstanceSets ) == 0 {
451- return errors .New (".spec.instances is a required value" ) // shouldn't happen as the value is required in the crd.yaml
452- }
453-
454- // Using minimal resources since the patroni version check pod is performing a very simple
455- // operation i.e. "patronictl version"
456- resources := corev1.ResourceRequirements {
457- Limits : corev1.ResourceList {
458- corev1 .ResourceCPU : resource .MustParse ("100m" ),
459- corev1 .ResourceMemory : resource .MustParse ("64Mi" ),
460- },
461- Requests : corev1.ResourceList {
462- corev1 .ResourceCPU : resource .MustParse ("50m" ),
463- corev1 .ResourceMemory : resource .MustParse ("32Mi" ),
464- },
465- }
466-
467- p = & corev1.Pod {
468- ObjectMeta : meta ,
469- Spec : corev1.PodSpec {
470- Containers : []corev1.Container {
471- {
472- Name : pNaming .ContainerPatroniVersionCheck ,
473- Image : cr .PostgresImage (),
474- Command : []string {
475- "bash" ,
476- },
477- Args : []string {
478- "-c" , "sleep 60" ,
479- },
480- Resources : resources ,
481- SecurityContext : initialize .RestrictedSecurityContext (cr .CompareVersion ("2.8.0" ) >= 0 ),
482- },
483- },
484- SecurityContext : cr .Spec .InstanceSets [0 ].SecurityContext ,
485- Affinity : cr .Spec .InstanceSets [0 ].Affinity ,
486- TerminationGracePeriodSeconds : ptr .To (int64 (5 )),
487- ImagePullSecrets : cr .Spec .ImagePullSecrets ,
488- Resources : & resources ,
489- },
490- }
491-
492- if err := controllerutil .SetControllerReference (cr , p , r .Client .Scheme ()); err != nil {
493- return errors .Wrap (err , "set controller reference" )
494- }
495- if err := r .Client .Create (ctx , p ); client .IgnoreAlreadyExists (err ) != nil {
496- return errors .Wrap (err , "failed to create pod to check patroni version" )
497- }
498-
499- return errPatroniVersionCheckWait
500- }
501-
502- if p .Status .Phase != corev1 .PodRunning {
503- return errPatroniVersionCheckWait
504- }
505-
506- var stdout , stderr bytes.Buffer
507- execCli , err := clientcmd .NewClient ()
508- if err != nil {
509- return errors .Wrap (err , "failed to create exec client" )
510- }
511- b := wait.Backoff {
512- Duration : 5 * time .Second ,
513- Factor : 1.0 ,
514- Steps : 12 ,
515- Cap : time .Minute ,
516- }
517- if err := retry .OnError (b , func (err error ) bool { return err != nil && strings .Contains (err .Error (), "container not found" ) }, func () error {
518- return execCli .Exec (ctx , p , pNaming .ContainerPatroniVersionCheck , nil , & stdout , & stderr , "patronictl" , "version" )
519- }); err != nil {
520- return errors .Wrap (err , "exec" )
521- }
522-
523- patroniVersion := strings .TrimSpace (strings .TrimPrefix (stdout .String (), "patronictl version " ))
524-
525- if _ , err := gover .NewVersion (patroniVersion ); err != nil {
526- return errors .Wrap (err , "failed to validate patroni version" )
527- }
528-
529- orig := cr .DeepCopy ()
530-
531- cr .Status .Patroni .Version = patroniVersion
532- cr .Status .PatroniVersion = patroniVersion
533- cr .Status .Postgres .Version = cr .Spec .PostgresVersion
534- cr .Status .Postgres .ImageID = getImageIDFromPod (p , pNaming .ContainerPatroniVersionCheck )
535-
536- if err := r .Client .Status ().Patch (ctx , cr .DeepCopy (), client .MergeFrom (orig )); err != nil {
537- return errors .Wrap (err , "failed to patch patroni version" )
538- }
539-
540- err = r .patchPatroniVersionAnnotation (ctx , cr , patroniVersion )
541- if err != nil {
542- return errors .Wrap (err , "failed to patch patroni version annotation" )
543- }
544-
545- if err := r .Client .Delete (ctx , p ); err != nil {
546- return errors .Wrap (err , "failed to delete patroni version check pod" )
547- }
548-
549- return nil
550- }
551-
552- func (r * PGClusterReconciler ) patchPatroniVersionAnnotation (ctx context.Context , cr * v2.PerconaPGCluster , patroniVersion string ) error {
553- orig := cr .DeepCopy ()
554- cr .Annotations [pNaming .AnnotationPatroniVersion ] = patroniVersion
555- if err := r .Client .Patch (ctx , cr .DeepCopy (), client .MergeFrom (orig )); err != nil {
556- return errors .Wrap (err , "failed to patch the pg cluster" )
557- }
558- return nil
559- }
560-
561- func (r * PGClusterReconciler ) instanceImageIDs (ctx context.Context , cr * v2.PerconaPGCluster ) ([]string , error ) {
562- pods := new (corev1.PodList )
563- instances , err := naming .AsSelector (naming .ClusterInstances (cr .Name ))
564- if err != nil {
565- return nil , errors .Wrap (err , "failed to create a selector for instance pods" )
566- }
567- if err = r .Client .List (ctx , pods , client .InNamespace (cr .Namespace ), client.MatchingLabelsSelector {Selector : instances }); err != nil {
568- return nil , errors .Wrap (err , "failed to list instances" )
569- }
570-
571- // Collecting all image IDs from instance pods. Under normal conditions, this slice will contain a single image ID, as all pods typically use the same image.
572- // During an image update, it may contain multiple different image IDs as the update progresses.
573- var imageIDs []string
574- for _ , pod := range pods .Items {
575- imageID := getImageIDFromPod (& pod , naming .ContainerDatabase )
576- if imageID != "" && ! slices .Contains (imageIDs , imageID ) {
577- imageIDs = append (imageIDs , imageID )
578- }
579- }
580-
581- return imageIDs , nil
582- }
583-
584- func getImageIDFromPod (pod * corev1.Pod , containerName string ) string {
585- idx := slices .IndexFunc (pod .Status .ContainerStatuses , func (s corev1.ContainerStatus ) bool {
586- return s .Name == containerName
587- })
588- if idx == - 1 {
589- return ""
590- }
591- return pod .Status .ContainerStatuses [idx ].ImageID
592- }
593-
594362func (r * PGClusterReconciler ) reconcileTLS (ctx context.Context , cr * v2.PerconaPGCluster ) error {
595363 if err := r .validateTLS (ctx , cr ); err != nil {
596364 return errors .Wrap (err , "validate TLS" )
0 commit comments